Introduction to Pandas

pandas is a Python package providing fast, flexible, and expressive data structures designed to work with relational or labeled data both. It is a fundamental high-level building block for doing practical, real world data analysis in Python.

pandas is well suited for:

  • Tabular data with heterogeneously-typed columns, as in an SQL table or Excel spreadsheet
  • Ordered and unordered (not necessarily fixed-frequency) time series data.
  • Arbitrary matrix data (homogeneously typed or heterogeneous) with row and column labels
  • Any other form of observational / statistical data sets. The data actually need not be labeled at all to be placed into a pandas data structure

Key features:

  • Easy handling of missing data
  • Size mutability: columns can be inserted and deleted from DataFrame and higher dimensional objects
  • Automatic and explicit data alignment: objects can be explicitly aligned to a set of labels, or the data can be aligned automatically
  • Powerful, flexible group by functionality to perform split-apply-combine operations on data sets
  • Intelligent label-based slicing, fancy indexing, and subsetting of large data sets
  • Intuitive merging and joining data sets
  • Flexible reshaping and pivoting of data sets
  • Hierarchical labeling of axes
  • Robust IO tools for loading data from flat files, Excel files, databases, and HDF5
  • Time series functionality: date range generation and frequency conversion, moving window statistics, moving window linear regressions, date shifting and lagging, etc.

In [49]:
import pandas as pd
import numpy as np
pd.options.mode.chained_assignment = None  # default='warn'

Pandas Data Structures

Series

A Series is a single vector of data (like a NumPy array) with an index that labels each element in the vector.


In [50]:
counts = pd.Series([632, 1638, 569, 115])
counts


Out[50]:
0     632
1    1638
2     569
3     115
dtype: int64

If an index is not specified, a default sequence of integers is assigned as the index. A NumPy array comprises the values of the Series, while the index is a pandas Index object.


In [51]:
counts.values


Out[51]:
array([ 632, 1638,  569,  115])

In [52]:
counts.index


Out[52]:
RangeIndex(start=0, stop=4, step=1)

We can assign meaningful labels to the index, if they are available:


In [53]:
bacteria = pd.Series([632, 1638, 569, 115], 
    index=['Firmicutes', 'Proteobacteria', 'Actinobacteria', 'Bacteroidetes'])

bacteria


Out[53]:
Firmicutes         632
Proteobacteria    1638
Actinobacteria     569
Bacteroidetes      115
dtype: int64

These labels can be used to refer to the values in the Series.


In [54]:
bacteria['Actinobacteria']


Out[54]:
569

In [55]:
bacteria


Out[55]:
Firmicutes         632
Proteobacteria    1638
Actinobacteria     569
Bacteroidetes      115
dtype: int64

In [56]:
bacteria[[name.endswith('bacteria') for name in bacteria.index]]


Out[56]:
Proteobacteria    1638
Actinobacteria     569
dtype: int64

In [57]:
[name.endswith('bacteria') for name in bacteria.index]


Out[57]:
[False, True, True, False]

Notice that the indexing operation preserved the association between the values and the corresponding indices.

We can still use positional indexing if we wish.


In [58]:
bacteria[0]


Out[58]:
632

We can give both the array of values and the index meaningful labels themselves:


In [59]:
bacteria.name = 'counts'
bacteria.index.name = 'phylum'
bacteria


Out[59]:
phylum
Firmicutes         632
Proteobacteria    1638
Actinobacteria     569
Bacteroidetes      115
Name: counts, dtype: int64

NumPy's math functions and other operations can be applied to Series without losing the data structure.


In [60]:
np.log(bacteria)


Out[60]:
phylum
Firmicutes        6.448889
Proteobacteria    7.401231
Actinobacteria    6.343880
Bacteroidetes     4.744932
Name: counts, dtype: float64

We can also filter according to the values in the Series:


In [61]:
bacteria[bacteria>1000]


Out[61]:
phylum
Proteobacteria    1638
Name: counts, dtype: int64

A Series can be thought of as an ordered key-value store. In fact, we can create one from a dict:


In [62]:
bacteria_dict = {'Firmicutes': 632, 'Proteobacteria': 1638, 'Actinobacteria': 569,
                 'Bacteroidetes': 115}
pd.Series(bacteria_dict)


Out[62]:
Actinobacteria     569
Bacteroidetes      115
Firmicutes         632
Proteobacteria    1638
dtype: int64

Notice that the Series is created in key-sorted order.

If we pass a custom index to Series, it will select the corresponding values from the dict, and treat indices without corrsponding values as missing. Pandas uses the NaN (not a number) type for missing values.


In [63]:
bacteria2 = pd.Series(bacteria_dict, 
                      index=['Cyanobacteria','Firmicutes',
                             'Proteobacteria','Actinobacteria'])
bacteria2


Out[63]:
Cyanobacteria        NaN
Firmicutes         632.0
Proteobacteria    1638.0
Actinobacteria     569.0
dtype: float64

In [64]:
bacteria2.isnull()


Out[64]:
Cyanobacteria      True
Firmicutes        False
Proteobacteria    False
Actinobacteria    False
dtype: bool

Critically, the labels are used to align data when used in operations with other Series objects:


In [65]:
bacteria + bacteria2


Out[65]:
Actinobacteria    1138.0
Bacteroidetes        NaN
Cyanobacteria        NaN
Firmicutes        1264.0
Proteobacteria    3276.0
dtype: float64

Contrast this with NumPy arrays, where arrays of the same length will combine values element-wise; adding Series combined values with the same label in the resulting series. Notice also that the missing values were propogated by addition.

DataFrame

Inevitably, we want to be able to store, view and manipulate data that is multivariate, where for every index there are multiple fields or columns of data, often of varying data type.

A DataFrame is a tabular data structure, encapsulating multiple series like columns in a spreadsheet. Data are stored internally as a 2-dimensional object, but the DataFrame allows us to represent and manipulate higher-dimensional data.


In [66]:
data = pd.DataFrame({'value':[623, 1638, 569, 115, 433, 1130, 754, 555],
                     'patient':[1, 1, 1, 1, 2, 2, 2, 2],
                     'phylum':['Firmicutes', 'Proteobacteria', 'Actinobacteria', 
    'Bacteroidetes', 'Firmicutes', 'Proteobacteria', 'Actinobacteria', 'Bacteroidetes']})
data


Out[66]:
patient phylum value
0 1 Firmicutes 623
1 1 Proteobacteria 1638
2 1 Actinobacteria 569
3 1 Bacteroidetes 115
4 2 Firmicutes 433
5 2 Proteobacteria 1130
6 2 Actinobacteria 754
7 2 Bacteroidetes 555

Notice the DataFrame is sorted by column name. We can change the order by indexing them in the order we desire:


In [67]:
data[['phylum','value','patient']]


Out[67]:
phylum value patient
0 Firmicutes 623 1
1 Proteobacteria 1638 1
2 Actinobacteria 569 1
3 Bacteroidetes 115 1
4 Firmicutes 433 2
5 Proteobacteria 1130 2
6 Actinobacteria 754 2
7 Bacteroidetes 555 2

A DataFrame has a second index, representing the columns:


In [68]:
data.columns


Out[68]:
Index(['patient', 'phylum', 'value'], dtype='object')

The dtypes attribute reveals the data type for each column in our DataFrame.

  • int64 is numeric integer values
  • object strings (letters and numbers)
  • float64 floating-point values

In [69]:
data.dtypes


Out[69]:
patient     int64
phylum     object
value       int64
dtype: object

If we wish to access columns, we can do so either by dict-like indexing or by attribute:


In [70]:
data['patient']


Out[70]:
0    1
1    1
2    1
3    1
4    2
5    2
6    2
7    2
Name: patient, dtype: int64

In [71]:
data.patient


Out[71]:
0    1
1    1
2    1
3    1
4    2
5    2
6    2
7    2
Name: patient, dtype: int64

In [72]:
type(data.value)


Out[72]:
pandas.core.series.Series

In [73]:
data[['value']]


Out[73]:
value
0 623
1 1638
2 569
3 115
4 433
5 1130
6 754
7 555

Notice this is different than with Series, where dict-like indexing retrieved a particular element (row).

If we want access to a row in a DataFrame, we index its loc attribute.


In [74]:
data.loc[0]


Out[74]:
patient             1
phylum     Firmicutes
value             623
Name: 0, dtype: object

Exercise 1

Try out these commands to see what they return:

  • data.head()
  • data.tail(3)
  • data.shape

In [75]:
data.head()


Out[75]:
patient phylum value
0 1 Firmicutes 623
1 1 Proteobacteria 1638
2 1 Actinobacteria 569
3 1 Bacteroidetes 115
4 2 Firmicutes 433

data.head shows all of the data up to a certain number, if it is indicated in the parantheses


In [76]:
data.tail(3)


Out[76]:
patient phylum value
5 2 Proteobacteria 1130
6 2 Actinobacteria 754
7 2 Bacteroidetes 555

this shows the last 3 rows of data of the data row


In [77]:
data.shape


Out[77]:
(8, 3)

this gives you the number of rows of data (8) and columns (3)

An alternative way of initializing a DataFrame is with a list of dicts:


In [78]:
data = pd.DataFrame([{'patient': 1, 'phylum': 'Firmicutes', 'value': 632},
                    {'patient': 1, 'phylum': 'Proteobacteria', 'value': 1638},
                    {'patient': 1, 'phylum': 'Actinobacteria', 'value': 569},
                    {'patient': 1, 'phylum': 'Bacteroidetes', 'value': 115},
                    {'patient': 2, 'phylum': 'Firmicutes', 'value': 433},
                    {'patient': 2, 'phylum': 'Proteobacteria', 'value': 1130},
                    {'patient': 2, 'phylum': 'Actinobacteria', 'value': 754},
                    {'patient': 2, 'phylum': 'Bacteroidetes', 'value': 555}])

In [79]:
data


Out[79]:
patient phylum value
0 1 Firmicutes 632
1 1 Proteobacteria 1638
2 1 Actinobacteria 569
3 1 Bacteroidetes 115
4 2 Firmicutes 433
5 2 Proteobacteria 1130
6 2 Actinobacteria 754
7 2 Bacteroidetes 555

Its important to note that the Series returned when a DataFrame is indexted is merely a view on the DataFrame, and not a copy of the data itself. So you must be cautious when manipulating this data:


In [80]:
vals = data.value
vals


Out[80]:
0     632
1    1638
2     569
3     115
4     433
5    1130
6     754
7     555
Name: value, dtype: int64

In [81]:
vals[5] = 0
vals


Out[81]:
0     632
1    1638
2     569
3     115
4     433
5       0
6     754
7     555
Name: value, dtype: int64

If we plan on modifying an extracted Series, its a good idea to make a copy.


In [82]:
vals = data.value.copy()
vals[5] = 1000
vals


Out[82]:
0     632
1    1638
2     569
3     115
4     433
5    1000
6     754
7     555
Name: value, dtype: int64

We can create or modify columns by assignment:


In [83]:
data.value[3,4,5,6] = [14, 21, 1130,5]
data
type(data)


Out[83]:
pandas.core.frame.DataFrame

In [84]:
data['year'] = 2013
data


Out[84]:
patient phylum value year
0 1 Firmicutes 632 2013
1 1 Proteobacteria 1638 2013
2 1 Actinobacteria 569 2013
3 1 Bacteroidetes 14 2013
4 2 Firmicutes 21 2013
5 2 Proteobacteria 1130 2013
6 2 Actinobacteria 5 2013
7 2 Bacteroidetes 555 2013

But note, we cannot use the attribute indexing method to add a new column:


In [85]:
data.treatment = 1
data


Out[85]:
patient phylum value year
0 1 Firmicutes 632 2013
1 1 Proteobacteria 1638 2013
2 1 Actinobacteria 569 2013
3 1 Bacteroidetes 14 2013
4 2 Firmicutes 21 2013
5 2 Proteobacteria 1130 2013
6 2 Actinobacteria 5 2013
7 2 Bacteroidetes 555 2013

In [86]:
data.treatment


Out[86]:
1

Exercise 2

From the data table above, create an index to return all rows for which the phylum name ends in "bacteria" and the value is greater than 1000.


In [87]:
# get all of the values from the data where the phylum name ends with bacteria
data[[phylum.endswith('bacteria') for phylum in data.phylum]]
# take all of the rows where the value which are above 1000
data[data.value>1000]


Out[87]:
patient phylum value year
1 1 Proteobacteria 1638 2013
5 2 Proteobacteria 1130 2013

In [88]:
data
data.phylum


Out[88]:
0        Firmicutes
1    Proteobacteria
2    Actinobacteria
3     Bacteroidetes
4        Firmicutes
5    Proteobacteria
6    Actinobacteria
7     Bacteroidetes
Name: phylum, dtype: object

Specifying a Series as a new columns cause its values to be added according to the DataFrame's index:


In [89]:
treatment = pd.Series([0]*4 + [1]*2)
treatment


Out[89]:
0    0
1    0
2    0
3    0
4    1
5    1
dtype: int64

In [90]:
data['treatment'] = treatment
data


Out[90]:
patient phylum value year treatment
0 1 Firmicutes 632 2013 0.0
1 1 Proteobacteria 1638 2013 0.0
2 1 Actinobacteria 569 2013 0.0
3 1 Bacteroidetes 14 2013 0.0
4 2 Firmicutes 21 2013 1.0
5 2 Proteobacteria 1130 2013 1.0
6 2 Actinobacteria 5 2013 NaN
7 2 Bacteroidetes 555 2013 NaN

Other Python data structures (ones without an index) need to be the same length as the DataFrame:


In [91]:
month = ['Jan', 'Feb', 'Mar', 'Apr']
data['month'] = month


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-91-360d03fdde9a> in <module>()
      1 month = ['Jan', 'Feb', 'Mar', 'Apr']
----> 2 data['month'] = month

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/frame.py in __setitem__(self, key, value)
   2355         else:
   2356             # set column
-> 2357             self._set_item(key, value)
   2358 
   2359     def _setitem_slice(self, key, value):

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/frame.py in _set_item(self, key, value)
   2421 
   2422         self._ensure_valid_index(value)
-> 2423         value = self._sanitize_column(key, value)
   2424         NDFrame._set_item(self, key, value)
   2425 

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/frame.py in _sanitize_column(self, key, value)
   2576 
   2577             # turn me into an ndarray
-> 2578             value = _sanitize_index(value, self.index, copy=False)
   2579             if not isinstance(value, (np.ndarray, Index)):
   2580                 if isinstance(value, list) and len(value) > 0:

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/series.py in _sanitize_index(data, index, copy)
   2768 
   2769     if len(data) != len(index):
-> 2770         raise ValueError('Length of values does not match length of ' 'index')
   2771 
   2772     if isinstance(data, PeriodIndex):

ValueError: Length of values does not match length of index

In [ ]:
data['month'] = ['Jan']*len(data)
data

We can use the drop method to remove rows or columns, which by default drops rows. We can be explicit by using the axis argument:


In [ ]:
data_nomonth = data.drop('month', axis=1)
data_nomonth

We can extract the underlying data as a simple ndarray by accessing the values attribute:


In [ ]:
data.values

Notice that because of the mix of string and integer (and NaN) values, the dtype of the array is object. The dtype will automatically be chosen to be as general as needed to accomodate all the columns.


In [ ]:
df = pd.DataFrame({'foo': [1,2,3], 'bar':[0.4, -1.0, 4.5]})
df.values

Pandas uses a custom data structure to represent the indices of Series and DataFrames.


In [ ]:
data.index

Index objects are immutable:


In [ ]:
data.index[0] = 15

This is so that Index objects can be shared between data structures without fear that they will be changed.


In [ ]:
bacteria2.index = bacteria.index

In [ ]:
bacteria2

Importing data

A key, but often under-appreciated, step in data analysis is importing the data that we wish to analyze. Though it is easy to load basic data structures into Python using built-in tools or those provided by packages like NumPy, it is non-trivial to import structured data well, and to easily convert this input into a robust data structure:

genes = np.loadtxt("genes.csv", delimiter=",", dtype=[('gene', '|S10'), ('value', '<f4')])

Pandas provides a convenient set of functions for importing tabular data in a number of formats directly into a DataFrame object. These functions include a slew of options to perform type inference, indexing, parsing, iterating and cleaning automatically as data are imported.

Let's start with some more bacteria data, stored in csv format.


In [ ]:
!cat Data/microbiome.csv

This table can be read into a DataFrame using read_csv:


In [ ]:
mb = pd.read_csv("data/microbiome.csv")
mb

Notice that read_csv automatically considered the first row in the file to be a header row.

We can override default behavior by customizing some the arguments, like header, names or index_col.


In [ ]:
pd.read_csv("Data/microbiome.csv", header=None).head()

read_csv is just a convenience function for read_table, since csv is such a common format:


In [ ]:
mb = pd.read_table("Data/microbiome.csv", sep=',')

The sep argument can be customized as needed to accomodate arbitrary separators. For example, we can use a regular expression to define a variable amount of whitespace, which is unfortunately very common in some data formats:

sep='\s+'

For a more useful index, we can specify the first two columns, which together provide a unique index to the data.


In [ ]:
mb = pd.read_csv("Data/microbiome.csv", index_col=['Patient','Taxon'])
mb.head(5)

This is called a hierarchical index, which we will revisit later in the section.

If we have sections of data that we do not wish to import (for example, known bad data), we can populate the skiprows argument:


In [ ]:
pd.read_csv("Data/microbiome.csv", skiprows=[3,4,6]).head()

If we only want to import a small number of rows from, say, a very large data file we can use nrows:


In [ ]:
pd.read_csv("Data/microbiome.csv", nrows=4)

Alternately, if we want to process our data in reasonable chunks, the chunksize argument will return an iterable object that can be employed in a data processing loop. For example, our microbiome data are organized by bacterial phylum, with 14 patients represented in each:


In [ ]:
pd.read_csv("Data/microbiome.csv", chunksize=14)

In [ ]:
data_chunks = pd.read_csv("Data/microbiome.csv", chunksize=14)

mean_tissue = pd.Series({chunk.Taxon[0]: chunk.Tissue.mean() for chunk in data_chunks})
    
mean_tissue

Most real-world data is incomplete, with values missing due to incomplete observation, data entry or transcription error, or other reasons. Pandas will automatically recognize and parse common missing data indicators, including NA and NULL.


In [ ]:
!cat Data/microbiome_missing.csv

In [ ]:
pd.read_csv("Data/microbiome_missing.csv").head(20)

Above, Pandas recognized NA and an empty field as missing data.


In [ ]:
pd.isnull(pd.read_csv("Data/microbiome_missing.csv")).head(20)

Unfortunately, there will sometimes be inconsistency with the conventions for missing data. In this example, there is a question mark "?" and a large negative number where there should have been a positive integer. We can specify additional symbols with the na_values argument:


In [ ]:
pd.read_csv("Data/microbiome_missing.csv", na_values=['?', -99999]).head(20)

These can be specified on a column-wise basis using an appropriate dict as the argument for na_values.

Microsoft Excel

Since so much financial and scientific data ends up in Excel spreadsheets (regrettably), Pandas' ability to directly import Excel spreadsheets is valuable. This support is contingent on having one or two dependencies (depending on what version of Excel file is being imported) installed: xlrd and openpyxl (these may be installed with either pip or easy_install).

The read_excel convenience function in pandas imports a specific sheet from an Excel file


In [ ]:
mb = pd.read_excel('Data/microbiome/MID2.xls', sheetname='Sheet 1', header=None)
mb.head()

There are several other data formats that can be imported into Python and converted into DataFrames, with the help of buitl-in or third-party libraries. These include JSON, XML, HDF5, relational and non-relational databases, and various web APIs. These are beyond the scope of this tutorial, but are covered in Python for Data Analysis.

Pandas Fundamentals

This section introduces the new user to the key functionality of Pandas that is required to use the software effectively.

For some variety, we will leave our digestive tract bacteria behind and employ some baseball data.


In [93]:
baseball = pd.read_csv("Data/baseball.csv", index_col='id')
baseball.head()


Out[93]:
player year stint team lg g ab r h X2b ... rbi sb cs bb so ibb hbp sh sf gidp
id
88641 womacto01 2006 2 CHN NL 19 50 6 14 1 ... 2.0 1.0 1.0 4 4.0 0.0 0.0 3.0 0.0 0.0
88643 schilcu01 2006 1 BOS AL 31 2 0 1 0 ... 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
88645 myersmi01 2006 1 NYA AL 62 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
88649 helliri01 2006 1 MIL NL 20 3 0 0 0 ... 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0 0.0 0.0
88650 johnsra05 2006 1 NYA AL 33 6 0 1 0 ... 0.0 0.0 0.0 0 4.0 0.0 0.0 0.0 0.0 0.0

5 rows × 22 columns


In [ ]:

Notice that we specified the id column as the index, since it appears to be a unique identifier. We could try to create a unique index ourselves by combining player and year:


In [94]:
player_id = baseball.player + baseball.year.astype(str)
baseball_newind = baseball.copy()
baseball_newind.index = player_id
baseball_newind.head()


Out[94]:
player year stint team lg g ab r h X2b ... rbi sb cs bb so ibb hbp sh sf gidp
womacto012006 womacto01 2006 2 CHN NL 19 50 6 14 1 ... 2.0 1.0 1.0 4 4.0 0.0 0.0 3.0 0.0 0.0
schilcu012006 schilcu01 2006 1 BOS AL 31 2 0 1 0 ... 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
myersmi012006 myersmi01 2006 1 NYA AL 62 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
helliri012006 helliri01 2006 1 MIL NL 20 3 0 0 0 ... 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0 0.0 0.0
johnsra052006 johnsra05 2006 1 NYA AL 33 6 0 1 0 ... 0.0 0.0 0.0 0 4.0 0.0 0.0 0.0 0.0 0.0

5 rows × 22 columns

This looks okay, but let's check:


In [95]:
baseball_newind.index.is_unique


Out[95]:
False

So, indices need not be unique. Our choice is not unique because some players change teams within years.


In [96]:
pd.Series(baseball_newind.index).value_counts()


Out[96]:
gomezch022007    2
claytro012007    2
wellsda012007    2
sweenma012007    2
loftoke012007    2
cirilje012007    2
hernaro012007    2
benitar012007    2
wickmbo012007    2
coninje012007    2
francju012007    2
trachst012007    2
easleda012007    1
gonzalu012007    1
myersmi012007    1
schilcu012007    1
timlimi012007    1
loaizes012007    1
sheffga012007    1
seleaa012006     1
stantmi022007    1
hoffmtr012007    1
wakefti012007    1
stairma012007    1
sosasa012007     1
maddugr012007    1
walketo042007    1
bondsba012007    1
valenjo032007    1
finlest012006    1
                ..
myersmi012006    1
mussimi012007    1
sandere022007    1
thomeji012007    1
gonzalu012006    1
embreal012007    1
seleaa012007     1
tavarju012007    1
ramirma022007    1
cormirh012007    1
martipe022007    1
witasja012007    1
ausmubr012007    1
johnsra052006    1
mesajo012007     1
floydcl012007    1
kleskry012007    1
greensh012007    1
schilcu012006    1
suppaje012007    1
johnsra052007    1
whiteri012007    1
graffto012007    1
vizquom012007    1
oliveda022007    1
finlest012007    1
delgaca012007    1
parkch012007     1
aloumo012007     1
whitero022007    1
dtype: int64

The most important consequence of a non-unique index is that indexing by label will return multiple values for some labels:


In [97]:
baseball_newind.loc['wickmbo012007']


Out[97]:
player year stint team lg g ab r h X2b ... rbi sb cs bb so ibb hbp sh sf gidp
wickmbo012007 wickmbo01 2007 2 ARI NL 8 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
wickmbo012007 wickmbo01 2007 1 ATL NL 47 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0

2 rows × 22 columns

We will learn more about indexing below.

We can create a truly unique index by combining player, team and year:


In [98]:
player_unique = baseball.player + baseball.team + baseball.year.astype(str)
baseball_newind = baseball.copy()
baseball_newind.index = player_unique
baseball_newind.head()


Out[98]:
player year stint team lg g ab r h X2b ... rbi sb cs bb so ibb hbp sh sf gidp
womacto01CHN2006 womacto01 2006 2 CHN NL 19 50 6 14 1 ... 2.0 1.0 1.0 4 4.0 0.0 0.0 3.0 0.0 0.0
schilcu01BOS2006 schilcu01 2006 1 BOS AL 31 2 0 1 0 ... 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
myersmi01NYA2006 myersmi01 2006 1 NYA AL 62 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
helliri01MIL2006 helliri01 2006 1 MIL NL 20 3 0 0 0 ... 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0 0.0 0.0
johnsra05NYA2006 johnsra05 2006 1 NYA AL 33 6 0 1 0 ... 0.0 0.0 0.0 0 4.0 0.0 0.0 0.0 0.0 0.0

5 rows × 22 columns


In [ ]:
baseball_newind.index.is_unique

We can create meaningful indices more easily using a hierarchical index; for now, we will stick with the numeric id field as our index.

Manipulating indices

Reindexing allows users to manipulate the data labels in a DataFrame. It forces a DataFrame to conform to the new index, and optionally, fill in missing data if requested.

A simple use of reindex is to alter the order of the rows:


In [ ]:
baseball.reindex(baseball.index[::-1]).head()

Notice that the id index is not sequential. Say we wanted to populate the table with every id value. We could specify and index that is a sequence from the first to the last id numbers in the database, and Pandas would fill in the missing data with NaN values:


In [ ]:
id_range = range(baseball.index.values.min(), baseball.index.values.max())
baseball.reindex(id_range).head()

Missing values can be filled as desired, either with selected values, or by rule:


In [ ]:
baseball.reindex(id_range, method='ffill', columns=['player','year']).head()

In [ ]:
baseball.reindex(id_range, fill_value='charliebrown', columns=['player']).head()

Keep in mind that reindex does not work if we pass a non-unique index series.

We can remove rows or columns via the drop method:


In [ ]:
baseball.shape

In [ ]:
baseball.drop([89525, 89526])

In [99]:
baseball.drop(['ibb','hbp'], axis=1)


Out[99]:
player year stint team lg g ab r h X2b X3b hr rbi sb cs bb so sh sf gidp
id
88641 womacto01 2006 2 CHN NL 19 50 6 14 1 0 1 2.0 1.0 1.0 4 4.0 3.0 0.0 0.0
88643 schilcu01 2006 1 BOS AL 31 2 0 1 0 0 0 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0
88645 myersmi01 2006 1 NYA AL 62 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
88649 helliri01 2006 1 MIL NL 20 3 0 0 0 0 0 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0
88650 johnsra05 2006 1 NYA AL 33 6 0 1 0 0 0 0.0 0.0 0.0 0 4.0 0.0 0.0 0.0
88652 finlest01 2006 1 SFN NL 139 426 66 105 21 12 6 40.0 7.0 0.0 46 55.0 3.0 4.0 6.0
88653 gonzalu01 2006 1 ARI NL 153 586 93 159 52 2 15 73.0 0.0 1.0 69 58.0 0.0 6.0 14.0
88662 seleaa01 2006 1 LAN NL 28 26 2 5 1 0 0 0.0 0.0 0.0 1 7.0 6.0 0.0 1.0
89177 francju01 2007 2 ATL NL 15 40 1 10 3 0 0 8.0 0.0 0.0 4 10.0 0.0 1.0 1.0
89178 francju01 2007 1 NYN NL 40 50 7 10 0 0 1 8.0 2.0 1.0 10 13.0 0.0 1.0 1.0
89330 zaungr01 2007 1 TOR AL 110 331 43 80 24 1 10 52.0 0.0 0.0 51 55.0 1.0 6.0 9.0
89333 witasja01 2007 1 TBA AL 3 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89334 williwo02 2007 1 HOU NL 33 59 3 6 0 0 1 2.0 0.0 0.0 0 25.0 5.0 0.0 1.0
89335 wickmbo01 2007 2 ARI NL 8 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89336 wickmbo01 2007 1 ATL NL 47 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89337 whitero02 2007 1 MIN AL 38 109 8 19 4 0 4 20.0 0.0 0.0 6 19.0 0.0 1.0 2.0
89338 whiteri01 2007 1 HOU NL 20 1 0 0 0 0 0 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0
89339 wellsda01 2007 2 LAN NL 7 15 2 4 1 0 0 1.0 0.0 0.0 0 6.0 0.0 0.0 0.0
89340 wellsda01 2007 1 SDN NL 22 38 1 4 0 0 0 0.0 0.0 0.0 0 12.0 4.0 0.0 0.0
89341 weathda01 2007 1 CIN NL 67 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89343 walketo04 2007 1 OAK AL 18 48 5 13 1 0 0 4.0 0.0 0.0 2 4.0 0.0 2.0 2.0
89345 wakefti01 2007 1 BOS AL 1 2 0 0 0 0 0 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0
89347 vizquom01 2007 1 SFN NL 145 513 54 126 18 3 4 51.0 14.0 6.0 44 48.0 14.0 3.0 14.0
89348 villoro01 2007 1 NYA AL 6 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89352 valenjo03 2007 1 NYN NL 51 166 18 40 11 1 3 18.0 2.0 1.0 15 28.0 1.0 1.0 5.0
89354 trachst01 2007 2 CHN NL 4 7 0 1 0 0 0 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0
89355 trachst01 2007 1 BAL AL 3 5 0 0 0 0 0 0.0 0.0 0.0 0 3.0 0.0 0.0 0.0
89359 timlimi01 2007 1 BOS AL 4 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89360 thomeji01 2007 1 CHA AL 130 432 79 119 19 0 35 96.0 0.0 1.0 95 134.0 0.0 3.0 10.0
89361 thomafr04 2007 1 TOR AL 155 531 63 147 30 0 26 95.0 0.0 0.0 81 94.0 0.0 5.0 14.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
89460 guarded01 2007 1 CIN NL 15 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89462 griffke02 2007 1 CIN NL 144 528 78 146 24 1 30 93.0 6.0 1.0 85 99.0 0.0 9.0 14.0
89463 greensh01 2007 1 NYN NL 130 446 62 130 30 1 10 46.0 11.0 1.0 37 62.0 1.0 1.0 14.0
89464 graffto01 2007 1 MIL NL 86 231 34 55 8 0 9 30.0 0.0 1.0 24 44.0 0.0 2.0 7.0
89465 gordoto01 2007 1 PHI NL 44 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89466 gonzalu01 2007 1 LAN NL 139 464 70 129 23 2 15 68.0 6.0 2.0 56 56.0 0.0 2.0 11.0
89467 gomezch02 2007 2 CLE AL 19 53 4 15 2 0 0 5.0 0.0 0.0 0 6.0 1.0 1.0 1.0
89468 gomezch02 2007 1 BAL AL 73 169 17 51 10 1 1 16.0 1.0 2.0 10 20.0 5.0 1.0 5.0
89469 glavito02 2007 1 NYN NL 33 56 3 12 1 0 0 4.0 0.0 0.0 6 5.0 12.0 1.0 0.0
89473 floydcl01 2007 1 CHN NL 108 282 40 80 10 1 9 45.0 0.0 0.0 35 47.0 0.0 0.0 6.0
89474 finlest01 2007 1 COL NL 43 94 9 17 3 0 1 2.0 0.0 0.0 8 4.0 0.0 0.0 2.0
89480 embreal01 2007 1 OAK AL 4 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89481 edmonji01 2007 1 SLN NL 117 365 39 92 15 2 12 53.0 0.0 2.0 41 75.0 2.0 3.0 9.0
89482 easleda01 2007 1 NYN NL 76 193 24 54 6 0 10 26.0 0.0 1.0 19 35.0 0.0 1.0 2.0
89489 delgaca01 2007 1 NYN NL 139 538 71 139 30 0 24 87.0 4.0 0.0 52 118.0 0.0 6.0 12.0
89493 cormirh01 2007 1 CIN NL 6 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89494 coninje01 2007 2 NYN NL 21 41 2 8 2 0 0 5.0 0.0 0.0 7 8.0 1.0 1.0 1.0
89495 coninje01 2007 1 CIN NL 80 215 23 57 11 1 6 32.0 4.0 0.0 20 28.0 1.0 6.0 4.0
89497 clemero02 2007 1 NYA AL 2 2 0 1 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89498 claytro01 2007 2 BOS AL 8 6 1 0 0 0 0 0.0 0.0 0.0 0 3.0 0.0 0.0 2.0
89499 claytro01 2007 1 TOR AL 69 189 23 48 14 0 1 12.0 2.0 1.0 14 50.0 3.0 3.0 8.0
89501 cirilje01 2007 2 ARI NL 28 40 6 8 4 0 0 6.0 0.0 0.0 4 6.0 0.0 0.0 1.0
89502 cirilje01 2007 1 MIN AL 50 153 18 40 9 2 2 21.0 2.0 0.0 15 13.0 3.0 2.0 9.0
89521 bondsba01 2007 1 SFN NL 126 340 75 94 14 0 28 66.0 5.0 0.0 132 54.0 0.0 2.0 13.0
89523 biggicr01 2007 1 HOU NL 141 517 68 130 31 3 10 50.0 4.0 3.0 23 112.0 7.0 5.0 5.0
89525 benitar01 2007 2 FLO NL 34 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89526 benitar01 2007 1 SFN NL 19 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0
89530 ausmubr01 2007 1 HOU NL 117 349 38 82 16 3 3 25.0 6.0 1.0 37 74.0 4.0 1.0 11.0
89533 aloumo01 2007 1 NYN NL 87 328 51 112 19 1 13 49.0 3.0 0.0 27 30.0 0.0 3.0 13.0
89534 alomasa02 2007 1 NYN NL 8 22 1 3 1 0 0 0.0 0.0 0.0 0 3.0 0.0 0.0 0.0

100 rows × 20 columns

Indexing and Selection

Indexing works analogously to indexing in NumPy arrays, except we can use the labels in the Index object to extract values in addition to arrays of integers.


In [100]:
# Sample Series object
hits = baseball_newind.h
hits


Out[100]:
womacto01CHN2006     14
schilcu01BOS2006      1
myersmi01NYA2006      0
helliri01MIL2006      0
johnsra05NYA2006      1
finlest01SFN2006    105
gonzalu01ARI2006    159
seleaa01LAN2006       5
francju01ATL2007     10
francju01NYN2007     10
zaungr01TOR2007      80
witasja01TBA2007      0
williwo02HOU2007      6
wickmbo01ARI2007      0
wickmbo01ATL2007      0
whitero02MIN2007     19
whiteri01HOU2007      0
wellsda01LAN2007      4
wellsda01SDN2007      4
weathda01CIN2007      0
walketo04OAK2007     13
wakefti01BOS2007      0
vizquom01SFN2007    126
villoro01NYA2007      0
valenjo03NYN2007     40
trachst01CHN2007      1
trachst01BAL2007      0
timlimi01BOS2007      0
thomeji01CHA2007    119
thomafr04TOR2007    147
                   ... 
guarded01CIN2007      0
griffke02CIN2007    146
greensh01NYN2007    130
graffto01MIL2007     55
gordoto01PHI2007      0
gonzalu01LAN2007    129
gomezch02CLE2007     15
gomezch02BAL2007     51
glavito02NYN2007     12
floydcl01CHN2007     80
finlest01COL2007     17
embreal01OAK2007      0
edmonji01SLN2007     92
easleda01NYN2007     54
delgaca01NYN2007    139
cormirh01CIN2007      0
coninje01NYN2007      8
coninje01CIN2007     57
clemero02NYA2007      1
claytro01BOS2007      0
claytro01TOR2007     48
cirilje01ARI2007      8
cirilje01MIN2007     40
bondsba01SFN2007     94
biggicr01HOU2007    130
benitar01FLO2007      0
benitar01SFN2007      0
ausmubr01HOU2007     82
aloumo01NYN2007     112
alomasa02NYN2007      3
Name: h, dtype: int64

In [ ]:
# Numpy-style indexing
hits[:3]

In [ ]:
# Indexing by label
hits[['womacto01CHN2006','schilcu01BOS2006']]

We can also slice with data labels, since they have an intrinsic order within the Index:


In [ ]:
hits['womacto01CHN2006':'gonzalu01ARI2006']

In [ ]:
hits['womacto01CHN2006':'gonzalu01ARI2006'] = 5
hits

In a DataFrame we can slice along either or both axes:


In [ ]:
baseball_newind[['h','ab']]

In [ ]:
baseball_newind[baseball_newind.ab>500]

For a more concise (and readable) syntax, we can use the new query method to perform selection on a DataFrame. Instead of having to type the fully-specified column, we can simply pass a string that describes what to select. The query above is then simply:


In [ ]:
baseball_newind.query('ab > 500')

The DataFrame.index and DataFrame.columns are placed in the query namespace by default. If you want to refer to a variable in the current namespace, you can prefix the variable with @:


In [ ]:
min_ab = 450

In [ ]:
baseball_newind.query('ab > @min_ab')

The indexing field loc allows us to select subsets of rows and columns in an intuitive way:


In [ ]:
baseball_newind.loc['gonzalu01ARI2006', ['h','X2b', 'X3b', 'hr']]

In [ ]:
baseball_newind.loc[:'myersmi01NYA2006', 'hr']

In addition to using loc to select rows and columns by label, pandas also allows indexing by position using the iloc attribute.

So, we can query rows and columns by absolute position, rather than by name:


In [ ]:
baseball_newind.iloc[:5, 5:8]

Exercise 3

You can use the isin method query a DataFrame based upon a list of values as follows:

data['phylum'].isin(['Firmacutes', 'Bacteroidetes'])

Use isin to find all players that played for the Los Angeles Dodgers (LAN) or the San Francisco Giants (SFN). How many records contain these values?


In [101]:
baseball_newind[baseball_newind['team'].isin(['LAN','SFN'])]
#there are 15 players who are either LAN or SFN


Out[101]:
player year stint team lg g ab r h X2b ... rbi sb cs bb so ibb hbp sh sf gidp
finlest01SFN2006 finlest01 2006 1 SFN NL 139 426 66 105 21 ... 40.0 7.0 0.0 46 55.0 2.0 2.0 3.0 4.0 6.0
seleaa01LAN2006 seleaa01 2006 1 LAN NL 28 26 2 5 1 ... 0.0 0.0 0.0 1 7.0 0.0 0.0 6.0 0.0 1.0
wellsda01LAN2007 wellsda01 2007 2 LAN NL 7 15 2 4 1 ... 1.0 0.0 0.0 0 6.0 0.0 0.0 0.0 0.0 0.0
vizquom01SFN2007 vizquom01 2007 1 SFN NL 145 513 54 126 18 ... 51.0 14.0 6.0 44 48.0 6.0 1.0 14.0 3.0 14.0
sweenma01LAN2007 sweenma01 2007 2 LAN NL 30 33 2 9 1 ... 3.0 0.0 0.0 1 11.0 0.0 0.0 0.0 0.0 0.0
sweenma01SFN2007 sweenma01 2007 1 SFN NL 76 90 18 23 8 ... 10.0 2.0 0.0 13 18.0 0.0 3.0 1.0 0.0 0.0
seaneru01LAN2007 seaneru01 2007 1 LAN NL 68 1 0 0 0 ... 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
schmija01LAN2007 schmija01 2007 1 LAN NL 6 7 1 1 0 ... 1.0 0.0 0.0 0 4.0 0.0 0.0 1.0 0.0 0.0
loaizes01LAN2007 loaizes01 2007 1 LAN NL 5 7 0 1 0 ... 2.0 0.0 0.0 0 2.0 0.0 0.0 2.0 0.0 1.0
kleskry01SFN2007 kleskry01 2007 1 SFN NL 116 362 51 94 27 ... 44.0 5.0 1.0 46 68.0 2.0 1.0 1.0 1.0 14.0
kentje01LAN2007 kentje01 2007 1 LAN NL 136 494 78 149 36 ... 79.0 1.0 3.0 57 61.0 4.0 5.0 0.0 6.0 17.0
hernaro01LAN2007 hernaro01 2007 2 LAN NL 22 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
gonzalu01LAN2007 gonzalu01 2007 1 LAN NL 139 464 70 129 23 ... 68.0 6.0 2.0 56 56.0 4.0 4.0 0.0 2.0 11.0
bondsba01SFN2007 bondsba01 2007 1 SFN NL 126 340 75 94 14 ... 66.0 5.0 0.0 132 54.0 43.0 3.0 0.0 2.0 13.0
benitar01SFN2007 benitar01 2007 1 SFN NL 19 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0

15 rows × 22 columns

Operations

DataFrame and Series objects allow for several operations to take place either on a single object, or between two or more objects.

For example, we can perform arithmetic on the elements of two objects, such as combining baseball statistics across years. First, let's (artificially) construct two Series, consisting of home runs hit in years 2006 and 2007, respectively:


In [102]:
hr2006 = baseball.loc[baseball.year==2006, 'hr']
hr2006.index = baseball.player[baseball.year==2006]

hr2007 = baseball.loc[baseball.year==2007, 'hr']
hr2007.index = baseball.player[baseball.year==2007]

In [103]:
hr2007


Out[103]:
player
francju01     0
francju01     1
zaungr01     10
witasja01     0
williwo02     1
wickmbo01     0
wickmbo01     0
whitero02     4
whiteri01     0
wellsda01     0
wellsda01     0
weathda01     0
walketo04     0
wakefti01     0
vizquom01     4
villoro01     0
valenjo03     3
trachst01     0
trachst01     0
timlimi01     0
thomeji01    35
thomafr04    26
tavarju01     0
sweenma01     0
sweenma01     2
suppaje01     0
stinnke01     1
stantmi02     0
stairma01    21
sprinru01     0
             ..
guarded01     0
griffke02    30
greensh01    10
graffto01     9
gordoto01     0
gonzalu01    15
gomezch02     0
gomezch02     1
glavito02     0
floydcl01     9
finlest01     1
embreal01     0
edmonji01    12
easleda01    10
delgaca01    24
cormirh01     0
coninje01     0
coninje01     6
clemero02     0
claytro01     0
claytro01     1
cirilje01     0
cirilje01     2
bondsba01    28
biggicr01    10
benitar01     0
benitar01     0
ausmubr01     3
aloumo01     13
alomasa02     0
Name: hr, dtype: int64

Now, let's add them together, in hopes of getting 2-year home run totals:


In [104]:
hr_total = hr2006 + hr2007
hr_total


Out[104]:
player
alomasa02     NaN
aloumo01      NaN
ausmubr01     NaN
benitar01     NaN
benitar01     NaN
biggicr01     NaN
bondsba01     NaN
cirilje01     NaN
cirilje01     NaN
claytro01     NaN
claytro01     NaN
clemero02     NaN
coninje01     NaN
coninje01     NaN
cormirh01     NaN
delgaca01     NaN
easleda01     NaN
edmonji01     NaN
embreal01     NaN
finlest01     7.0
floydcl01     NaN
francju01     NaN
francju01     NaN
glavito02     NaN
gomezch02     NaN
gomezch02     NaN
gonzalu01    30.0
gordoto01     NaN
graffto01     NaN
greensh01     NaN
             ... 
sosasa01      NaN
sprinru01     NaN
stairma01     NaN
stantmi02     NaN
stinnke01     NaN
suppaje01     NaN
sweenma01     NaN
sweenma01     NaN
tavarju01     NaN
thomafr04     NaN
thomeji01     NaN
timlimi01     NaN
trachst01     NaN
trachst01     NaN
valenjo03     NaN
villoro01     NaN
vizquom01     NaN
wakefti01     NaN
walketo04     NaN
weathda01     NaN
wellsda01     NaN
wellsda01     NaN
whiteri01     NaN
whitero02     NaN
wickmbo01     NaN
wickmbo01     NaN
williwo02     NaN
witasja01     NaN
womacto01     NaN
zaungr01      NaN
Name: hr, dtype: float64

Pandas' data alignment places NaN values for labels that do not overlap in the two Series. In fact, there are only 6 players that occur in both years.


In [105]:
hr_total[hr_total.notnull()]


Out[105]:
player
finlest01     7.0
gonzalu01    30.0
johnsra05     0.0
myersmi01     0.0
schilcu01     0.0
seleaa01      0.0
Name: hr, dtype: float64

While we do want the operation to honor the data labels in this way, we probably do not want the missing values to be filled with NaN. We can use the add method to calculate player home run totals by using the fill_value argument to insert a zero for home runs where labels do not overlap:


In [106]:
hr2007.add(hr2006, fill_value=0)


Out[106]:
player
alomasa02     0.0
aloumo01     13.0
ausmubr01     3.0
benitar01     0.0
benitar01     0.0
biggicr01    10.0
bondsba01    28.0
cirilje01     0.0
cirilje01     2.0
claytro01     0.0
claytro01     1.0
clemero02     0.0
coninje01     0.0
coninje01     6.0
cormirh01     0.0
delgaca01    24.0
easleda01    10.0
edmonji01    12.0
embreal01     0.0
finlest01     7.0
floydcl01     9.0
francju01     0.0
francju01     1.0
glavito02     0.0
gomezch02     0.0
gomezch02     1.0
gonzalu01    30.0
gordoto01     0.0
graffto01     9.0
greensh01    10.0
             ... 
sosasa01     21.0
sprinru01     0.0
stairma01    21.0
stantmi02     0.0
stinnke01     1.0
suppaje01     0.0
sweenma01     0.0
sweenma01     2.0
tavarju01     0.0
thomafr04    26.0
thomeji01    35.0
timlimi01     0.0
trachst01     0.0
trachst01     0.0
valenjo03     3.0
villoro01     0.0
vizquom01     4.0
wakefti01     0.0
walketo04     0.0
weathda01     0.0
wellsda01     0.0
wellsda01     0.0
whiteri01     0.0
whitero02     4.0
wickmbo01     0.0
wickmbo01     0.0
williwo02     1.0
witasja01     0.0
womacto01     1.0
zaungr01     10.0
Name: hr, dtype: float64

Operations can also be broadcast between rows or columns.

For example, if we subtract the maximum number of home runs hit from the hr column, we get how many fewer than the maximum were hit by each player:


In [107]:
baseball.hr - baseball.hr.max()


Out[107]:
id
88641   -34
88643   -35
88645   -35
88649   -35
88650   -35
88652   -29
88653   -20
88662   -35
89177   -35
89178   -34
89330   -25
89333   -35
89334   -34
89335   -35
89336   -35
89337   -31
89338   -35
89339   -35
89340   -35
89341   -35
89343   -35
89345   -35
89347   -31
89348   -35
89352   -32
89354   -35
89355   -35
89359   -35
89360     0
89361    -9
         ..
89460   -35
89462    -5
89463   -25
89464   -26
89465   -35
89466   -20
89467   -35
89468   -34
89469   -35
89473   -26
89474   -34
89480   -35
89481   -23
89482   -25
89489   -11
89493   -35
89494   -35
89495   -29
89497   -35
89498   -35
89499   -34
89501   -35
89502   -33
89521    -7
89523   -25
89525   -35
89526   -35
89530   -32
89533   -22
89534   -35
Name: hr, dtype: int64

Or, looking at things row-wise, we can see how a particular player compares with the rest of the group with respect to important statistics


In [108]:
baseball.loc[89521, "player"]


Out[108]:
'bondsba01'

In [109]:
stats = baseball[['h','X2b', 'X3b', 'hr']]
diff = stats - stats.loc[89521]
diff[:10]


Out[109]:
h X2b X3b hr
id
88641 -80 -13 0 -27
88643 -93 -14 0 -28
88645 -94 -14 0 -28
88649 -94 -14 0 -28
88650 -93 -14 0 -28
88652 11 7 12 -22
88653 65 38 2 -13
88662 -89 -13 0 -28
89177 -84 -11 0 -28
89178 -84 -14 0 -27

We can also apply functions to each column or row of a DataFrame


In [110]:
stats.apply(np.median)


Out[110]:
h      8.0
X2b    1.0
X3b    0.0
hr     0.0
dtype: float64

In [111]:
def range_calc(x):
    return x.max() - x.min()

In [112]:
stat_range = lambda x: x.max() - x.min()
stats.apply(stat_range)


Out[112]:
h      159
X2b     52
X3b     12
hr      35
dtype: int64

Lets use apply to calculate a meaningful baseball statistics, slugging percentage:

$$SLG = \frac{1B + (2 \times 2B) + (3 \times 3B) + (4 \times HR)}{AB}$$

And just for fun, we will format the resulting estimate.


In [113]:
def slugging(x): 
    bases = x['h']-x['X2b']-x['X3b']-x['hr'] + 2*x['X2b'] + 3*x['X3b'] + 4*x['hr']
    ab = x['ab']+1e-6
    
    return bases/ab

baseball.apply(slugging, axis=1).round(3)


Out[113]:
id
88641    0.360
88643    0.500
88645    0.000
88649    0.000
88650    0.167
88652    0.394
88653    0.444
88662    0.231
89177    0.325
89178    0.260
89330    0.411
89333    0.000
89334    0.153
89335    0.000
89336    0.000
89337    0.321
89338    0.000
89339    0.333
89340    0.105
89341    0.000
89343    0.292
89345    0.000
89347    0.316
89348    0.000
89352    0.373
89354    0.143
89355    0.000
89359    0.000
89360    0.562
89361    0.480
         ...  
89460    0.000
89462    0.496
89463    0.430
89464    0.390
89465    0.000
89466    0.433
89467    0.321
89468    0.391
89469    0.232
89473    0.422
89474    0.245
89480    0.000
89481    0.403
89482    0.466
89489    0.448
89493    0.000
89494    0.244
89495    0.409
89497    0.500
89498    0.000
89499    0.344
89501    0.300
89502    0.386
89521    0.565
89523    0.381
89525    0.000
89526    0.000
89530    0.324
89533    0.524
89534    0.182
dtype: float64

Sorting and Ranking

Pandas objects include methods for re-ordering data.


In [ ]:
baseball_newind.sort_index().head()

In [ ]:
baseball_newind.sort_index(ascending=False).head()

Try sorting the columns instead of the rows, in ascending order:


In [ ]:
baseball_newind.sort_index(axis=1).head()

We can also use sort_values to sort a Series by value, rather than by label.


In [ ]:
baseball.hr.sort_values()

For a DataFrame, we can sort according to the values of one or more columns using the by argument of sort_values:


In [ ]:
baseball[['player','sb','cs']].sort_values(ascending=[False,True], 
                                           by=['sb', 'cs']).head(10)

Ranking does not re-arrange data, but instead returns an index that ranks each value relative to others in the Series.


In [114]:
baseball.hr.rank()


Out[114]:
id
88641     62.5
88643     29.0
88645     29.0
88649     29.0
88650     29.0
88652     76.0
88653     89.5
88662     29.0
89177     29.0
89178     62.5
89330     83.5
89333     29.0
89334     62.5
89335     29.0
89336     29.0
89337     73.5
89338     29.0
89339     29.0
89340     29.0
89341     29.0
89343     29.0
89345     29.0
89347     73.5
89348     29.0
89352     71.5
89354     29.0
89355     29.0
89359     29.0
89360    100.0
89361     97.0
         ...  
89460     29.0
89462     99.0
89463     83.5
89464     80.5
89465     29.0
89466     89.5
89467     29.0
89468     62.5
89469     29.0
89473     80.5
89474     62.5
89480     29.0
89481     87.0
89482     83.5
89489     95.0
89493     29.0
89494     29.0
89495     76.0
89497     29.0
89498     29.0
89499     62.5
89501     29.0
89502     69.0
89521     98.0
89523     83.5
89525     29.0
89526     29.0
89530     71.5
89533     88.0
89534     29.0
Name: hr, dtype: float64

Ties are assigned the mean value of the tied ranks, which may result in decimal values.


In [ ]:
pd.Series([100,100]).rank()

Alternatively, you can break ties via one of several methods, such as by the order in which they occur in the dataset:


In [ ]:
baseball.hr.rank(method='first')

Calling the DataFrame's rank method results in the ranks of all columns:


In [ ]:
baseball.rank(ascending=False).head()

In [ ]:
baseball[['r','h','hr']].rank(ascending=False).head()

Exercise 4

Calculate on base percentage for each player, and return the ordered series of estimates.

$$OBP = \frac{H + BB + HBP}{AB + BB + HBP + SF}$$

In [116]:
def onbasepercent(x):
    onbases = x['h'] + x['bb'] + x['hbp']
    basetotal = x['ab'] + x['bb'] + x['hbp'] + x['sf']+1e-6
    
    return onbases/basetotal

baseball.apply(onbasepercent, axis=1).round(3)


Out[116]:
id
88641    0.333
88643    0.500
88645    0.000
88649    0.000
88650    0.167
88652    0.320
88653    0.352
88662    0.222
89177    0.311
89178    0.328
89330    0.341
89333    0.000
89334    0.102
89335    0.000
89336    0.000
89337    0.235
89338    0.000
89339    0.267
89340    0.105
89341    0.000
89343    0.288
89345    0.000
89347    0.305
89348    0.000
89352    0.302
89354    0.143
89355    0.000
89359    0.000
89360    0.410
89361    0.377
         ...  
89460    0.000
89462    0.372
89463    0.352
89464    0.315
89465    0.000
89466    0.359
89467    0.278
89468    0.339
89469    0.286
89473    0.373
89474    0.245
89480    0.000
89481    0.325
89482    0.358
89489    0.333
89493    0.000
89494    0.306
89495    0.320
89497    0.500
89498    0.000
89499    0.304
89501    0.273
89502    0.327
89521    0.480
89523    0.285
89525    0.000
89526    0.000
89530    0.318
89533    0.392
89534    0.136
dtype: float64

Hierarchical indexing

In the baseball example, I was forced to combine 3 fields to obtain a unique index that was not simply an integer value. A more elegant way to have done this would be to create a hierarchical index from the three fields.


In [117]:
baseball_h = baseball.set_index(['year', 'team', 'player'])
baseball_h.head(10)


Out[117]:
stint lg g ab r h X2b X3b hr rbi sb cs bb so ibb hbp sh sf gidp
year team player
2006 CHN womacto01 2 NL 19 50 6 14 1 0 1 2.0 1.0 1.0 4 4.0 0.0 0.0 3.0 0.0 0.0
BOS schilcu01 1 AL 31 2 0 1 0 0 0 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
NYA myersmi01 1 AL 62 0 0 0 0 0 0 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
MIL helliri01 1 NL 20 3 0 0 0 0 0 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0 0.0 0.0
NYA johnsra05 1 AL 33 6 0 1 0 0 0 0.0 0.0 0.0 0 4.0 0.0 0.0 0.0 0.0 0.0
SFN finlest01 1 NL 139 426 66 105 21 12 6 40.0 7.0 0.0 46 55.0 2.0 2.0 3.0 4.0 6.0
ARI gonzalu01 1 NL 153 586 93 159 52 2 15 73.0 0.0 1.0 69 58.0 10.0 7.0 0.0 6.0 14.0
LAN seleaa01 1 NL 28 26 2 5 1 0 0 0.0 0.0 0.0 1 7.0 0.0 0.0 6.0 0.0 1.0
2007 ATL francju01 2 NL 15 40 1 10 3 0 0 8.0 0.0 0.0 4 10.0 1.0 0.0 0.0 1.0 1.0
NYN francju01 1 NL 40 50 7 10 0 0 1 8.0 2.0 1.0 10 13.0 0.0 0.0 0.0 1.0 1.0

This index is a MultiIndex object that consists of a sequence of tuples, the elements of which is some combination of the three columns used to create the index. Where there are multiple repeated values, Pandas does not print the repeats, making it easy to identify groups of values.


In [118]:
baseball_h.index[:10]


Out[118]:
MultiIndex(levels=[[2006, 2007], ['ARI', 'ATL', 'BAL', 'BOS', 'CHA', 'CHN', 'CIN', 'CLE', 'COL', 'DET', 'FLO', 'HOU', 'KCA', 'LAA', 'LAN', 'MIL', 'MIN', 'NYA', 'NYN', 'OAK', 'PHI', 'SDN', 'SFN', 'SLN', 'TBA', 'TEX', 'TOR'], ['alomasa02', 'aloumo01', 'ausmubr01', 'benitar01', 'biggicr01', 'bondsba01', 'cirilje01', 'claytro01', 'clemero02', 'coninje01', 'cormirh01', 'delgaca01', 'easleda01', 'edmonji01', 'embreal01', 'finlest01', 'floydcl01', 'francju01', 'glavito02', 'gomezch02', 'gonzalu01', 'gordoto01', 'graffto01', 'greensh01', 'griffke02', 'guarded01', 'helliri01', 'hernaro01', 'hoffmtr01', 'johnsra05', 'jonesto02', 'kentje01', 'kleskry01', 'loaizes01', 'loftoke01', 'mabryjo01', 'maddugr01', 'martipe02', 'mesajo01', 'moyerja01', 'mussimi01', 'myersmi01', 'oliveda02', 'parkch01', 'perezne01', 'piazzmi01', 'ramirma02', 'rodriiv01', 'rogerke01', 'sandere02', 'schilcu01', 'schmija01', 'seaneru01', 'seleaa01', 'sheffga01', 'smoltjo01', 'sosasa01', 'sprinru01', 'stairma01', 'stantmi02', 'stinnke01', 'suppaje01', 'sweenma01', 'tavarju01', 'thomafr04', 'thomeji01', 'timlimi01', 'trachst01', 'valenjo03', 'villoro01', 'vizquom01', 'wakefti01', 'walketo04', 'weathda01', 'wellsda01', 'whiteri01', 'whitero02', 'wickmbo01', 'williwo02', 'witasja01', 'womacto01', 'zaungr01']],
           labels=[[0, 0, 0, 0, 0, 0, 0, 0, 1, 1], [5, 3, 17, 15, 17, 22, 0, 14, 1, 18], [80, 50, 41, 26, 29, 15, 20, 53, 17, 17]],
           names=['year', 'team', 'player'])

In [209]:
baseball_h.index.is_unique


Out[209]:
True

Try using this hierarchical index to retrieve Julio Franco (francju01), who played for the Atlanta Braves (ATL) in 2007:


In [210]:
baseball_h.loc[(2007, 'ATL', 'francju01')]


Out[210]:
stint     2
lg       NL
g        15
ab       40
r         1
h        10
X2b       3
X3b       0
hr        0
rbi       8
sb        0
cs        0
bb        4
so       10
ibb       1
hbp       0
sh        0
sf        1
gidp      1
Name: (2007, ATL, francju01), dtype: object

Recall earlier we imported some microbiome data using two index columns. This created a 2-level hierarchical index:


In [211]:
mb = pd.read_csv("Data/microbiome.csv", index_col=['Taxon','Patient'])

In [212]:
mb.head(10)


Out[212]:
Group Tissue Stool
Taxon Patient
Firmicutes 1 0 136 4182
2 1 1174 703
3 0 408 3946
4 1 831 8605
5 0 693 50
6 1 718 717
7 0 173 33
8 1 228 80
9 0 162 3196
10 1 372 32

With a hierachical index, we can select subsets of the data based on a partial index:


In [213]:
mb.loc['Proteobacteria']


Out[213]:
Group Tissue Stool
Patient
1 0 2469 1821
2 1 839 661
3 0 4414 18
4 1 12044 83
5 0 2310 12
6 1 3053 547
7 0 395 2174
8 1 2651 767
9 0 1195 76
10 1 6857 795
11 0 483 666
12 1 2950 3994
13 0 1541 816
14 1 1307 53

Hierarchical indices can be created on either or both axes. Here is a trivial example:


In [214]:
frame = pd.DataFrame(np.arange(12).reshape(( 4, 3)), 
                  index =[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], 
                  columns =[['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']])

frame


Out[214]:
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11

If you want to get fancy, both the row and column indices themselves can be given names:


In [215]:
frame.index.names = ['key1', 'key2']
frame.columns.names = ['state', 'color']
frame


Out[215]:
state Ohio Colorado
color Green Red Green
key1 key2
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11

With this, we can do all sorts of custom indexing:


In [267]:
frame.loc['a', 'Ohio']


Out[267]:
color Green Red
key2
1 0 1
2 3 4

Try retrieving the value corresponding to b2 in Colorado:


In [264]:
# Write your answer here
frame.loc['b','2','Colorado']


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in _has_valid_type(self, key, axis)
   1394                 if key not in ax:
-> 1395                     error()
   1396             except TypeError as e:

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in error()
   1389                 raise KeyError("the label [%s] is not in the [%s]" %
-> 1390                                (key, self.obj._get_axis_name(axis)))
   1391 

KeyError: 'the label [2] is not in the [index]'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-264-3cd6e1a66513> in <module>()
      1 # Write your answer here
----> 2 frame.loc['2','Colorado']

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in __getitem__(self, key)
   1292 
   1293         if type(key) is tuple:
-> 1294             return self._getitem_tuple(key)
   1295         else:
   1296             return self._getitem_axis(key, axis=0)

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in _getitem_tuple(self, tup)
    782     def _getitem_tuple(self, tup):
    783         try:
--> 784             return self._getitem_lowerdim(tup)
    785         except IndexingError:
    786             pass

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in _getitem_lowerdim(self, tup)
    906         for i, key in enumerate(tup):
    907             if is_label_like(key) or isinstance(key, tuple):
--> 908                 section = self._getitem_axis(key, axis=i)
    909 
    910                 # we have yielded a scalar ?

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in _getitem_axis(self, key, axis)
   1464 
   1465         # fall thru to straight lookup
-> 1466         self._has_valid_type(key, axis)
   1467         return self._get_label(key, axis=axis)
   1468 

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in _has_valid_type(self, key, axis)
   1401                 raise
   1402             except:
-> 1403                 error()
   1404 
   1405         return True

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/indexing.py in error()
   1388                                     "key")
   1389                 raise KeyError("the label [%s] is not in the [%s]" %
-> 1390                                (key, self.obj._get_axis_name(axis)))
   1391 
   1392             try:

KeyError: 'the label [2] is not in the [index]'

Additionally, the order of the set of indices in a hierarchical MultiIndex can be changed by swapping them pairwise:


In [250]:
mb.swaplevel('Patient', 'Taxon').head()


Out[250]:
Group Tissue Stool
Patient Taxon
1 Firmicutes 0 136 4182
2 Firmicutes 1 1174 703
3 Firmicutes 0 408 3946
4 Firmicutes 1 831 8605
5 Firmicutes 0 693 50

Data can also be sorted by any index level, using sortlevel:


In [251]:
mb.sortlevel('Patient', ascending=False).head()


Out[251]:
Group Tissue Stool
Taxon Patient
Proteobacteria 14 1 1307 53
Other 14 1 305 32
Firmicutes 14 1 281 2377
Bacteroidetes 14 1 102 33
Actinobacteria 14 1 310 204

Missing data

The occurence of missing data is so prevalent that it pays to use tools like Pandas, which seamlessly integrates missing data handling so that it can be dealt with easily, and in the manner required by the analysis at hand.

Missing data are represented in Series and DataFrame objects by the NaN floating point value. However, None is also treated as missing, since it is commonly used as such in other contexts (e.g. NumPy).


In [252]:
foo = pd.Series([np.nan, -3, None, 'foobar'])
foo


Out[252]:
0       NaN
1        -3
2      None
3    foobar
dtype: object

In [253]:
foo.isnull()


Out[253]:
0     True
1    False
2     True
3    False
dtype: bool

Missing values may be dropped or indexed out:


In [254]:
bacteria2


Out[254]:
Firmicutes           NaN
Proteobacteria     632.0
Actinobacteria    1638.0
Bacteroidetes      569.0
dtype: float64

In [255]:
bacteria2.dropna()


Out[255]:
Proteobacteria     632.0
Actinobacteria    1638.0
Bacteroidetes      569.0
dtype: float64

In [256]:
bacteria2.isnull()


Out[256]:
Firmicutes         True
Proteobacteria    False
Actinobacteria    False
Bacteroidetes     False
dtype: bool

In [257]:
bacteria2[bacteria2.notnull()]


Out[257]:
Proteobacteria     632.0
Actinobacteria    1638.0
Bacteroidetes      569.0
dtype: float64

By default, dropna drops entire rows in which one or more values are missing.


In [258]:
data.dropna()


Out[258]:
patient phylum value year treatment month
0 1 Firmicutes 632 2013 0.0 Jan
1 1 Proteobacteria 1638 2013 0.0 Jan
2 1 Actinobacteria 569 2013 0.0 Jan
3 1 Bacteroidetes 14 2013 0.0 Jan
4 2 Firmicutes 21 2013 1.0 Jan
5 2 Proteobacteria 1130 2013 1.0 Jan

This can be overridden by passing the how='all' argument, which only drops a row when every field is a missing value.


In [259]:
data.dropna(how='all')


Out[259]:
patient phylum value year treatment month
0 1 Firmicutes 632 2013 0.0 Jan
1 1 Proteobacteria 1638 2013 0.0 Jan
2 1 Actinobacteria 569 2013 0.0 Jan
3 1 Bacteroidetes 14 2013 0.0 Jan
4 2 Firmicutes 21 2013 1.0 Jan
5 2 Proteobacteria 1130 2013 1.0 Jan
6 2 Actinobacteria 5 2013 NaN Jan
7 2 Bacteroidetes 555 2013 NaN Jan

This can be customized further by specifying how many values need to be present before a row is dropped via the thresh argument.


In [260]:
data.loc[7, 'year'] = np.nan
data


Out[260]:
patient phylum value year treatment month
0 1 Firmicutes 632 2013.0 0.0 Jan
1 1 Proteobacteria 1638 2013.0 0.0 Jan
2 1 Actinobacteria 569 2013.0 0.0 Jan
3 1 Bacteroidetes 14 2013.0 0.0 Jan
4 2 Firmicutes 21 2013.0 1.0 Jan
5 2 Proteobacteria 1130 2013.0 1.0 Jan
6 2 Actinobacteria 5 2013.0 NaN Jan
7 2 Bacteroidetes 555 NaN NaN Jan

In [261]:
data.dropna(thresh=5)


Out[261]:
patient phylum value year treatment month
0 1 Firmicutes 632 2013.0 0.0 Jan
1 1 Proteobacteria 1638 2013.0 0.0 Jan
2 1 Actinobacteria 569 2013.0 0.0 Jan
3 1 Bacteroidetes 14 2013.0 0.0 Jan
4 2 Firmicutes 21 2013.0 1.0 Jan
5 2 Proteobacteria 1130 2013.0 1.0 Jan
6 2 Actinobacteria 5 2013.0 NaN Jan

This is typically used in time series applications, where there are repeated measurements that are incomplete for some subjects.

Exercise 5

Try using the axis argument to drop columns with missing values:


In [119]:
# Write your answer here
data.dropna(axis=1)


Out[119]:
patient phylum value year
0 1 Firmicutes 632 2013
1 1 Proteobacteria 1638 2013
2 1 Actinobacteria 569 2013
3 1 Bacteroidetes 14 2013
4 2 Firmicutes 21 2013
5 2 Proteobacteria 1130 2013
6 2 Actinobacteria 5 2013
7 2 Bacteroidetes 555 2013

Rather than omitting missing data from an analysis, in some cases it may be suitable to fill the missing value in, either with a default value (such as zero) or a value that is either imputed or carried forward/backward from similar data points. We can do this programmatically in Pandas with the fillna argument.


In [269]:
bacteria2.fillna(0)


Out[269]:
Firmicutes           0.0
Proteobacteria     632.0
Actinobacteria    1638.0
Bacteroidetes      569.0
dtype: float64

In [270]:
data.fillna({'year': 2013, 'treatment':2})


Out[270]:
patient phylum value year treatment month
0 1 Firmicutes 632 2013.0 0.0 Jan
1 1 Proteobacteria 1638 2013.0 0.0 Jan
2 1 Actinobacteria 569 2013.0 0.0 Jan
3 1 Bacteroidetes 14 2013.0 0.0 Jan
4 2 Firmicutes 21 2013.0 1.0 Jan
5 2 Proteobacteria 1130 2013.0 1.0 Jan
6 2 Actinobacteria 5 2013.0 2.0 Jan
7 2 Bacteroidetes 555 2013.0 2.0 Jan

Notice that fillna by default returns a new object with the desired filling behavior, rather than changing the Series or DataFrame in place (in general, we like to do this, by the way!).

We can alter values in-place using inplace=True.


In [271]:
data.year.fillna(2013, inplace=True)
data


Out[271]:
patient phylum value year treatment month
0 1 Firmicutes 632 2013.0 0.0 Jan
1 1 Proteobacteria 1638 2013.0 0.0 Jan
2 1 Actinobacteria 569 2013.0 0.0 Jan
3 1 Bacteroidetes 14 2013.0 0.0 Jan
4 2 Firmicutes 21 2013.0 1.0 Jan
5 2 Proteobacteria 1130 2013.0 1.0 Jan
6 2 Actinobacteria 5 2013.0 NaN Jan
7 2 Bacteroidetes 555 2013.0 NaN Jan

Missing values can also be interpolated, using any one of a variety of methods:


In [272]:
bacteria2.fillna(method='bfill')


Out[272]:
Firmicutes         632.0
Proteobacteria     632.0
Actinobacteria    1638.0
Bacteroidetes      569.0
dtype: float64

Data summarization

We often wish to summarize data in Series or DataFrame objects, so that they can more easily be understood or compared with similar data. The NumPy package contains several functions that are useful here, but several summarization or reduction methods are built into Pandas data structures.


In [273]:
baseball.sum()


Out[273]:
player    womacto01schilcu01myersmi01helliri01johnsra05f...
year                                                 200692
stint                                                   113
team      CHNBOSNYAMILNYASFNARILANATLNYNTORTBAHOUARIATLM...
lg        NLALALNLALNLNLNLNLNLALALNLNLNLALNLNLNLNLALALNL...
g                                                      5238
ab                                                    13654
r                                                      1869
h                                                      3582
X2b                                                     739
X3b                                                      55
hr                                                      437
rbi                                                    1847
sb                                                      138
cs                                                       46
bb                                                     1549
so                                                     2408
ibb                                                     177
hbp                                                     112
sh                                                      138
sf                                                      120
gidp                                                    354
dtype: object

Clearly, sum is more meaningful for some columns than others. For methods like mean for which application to string variables is not just meaningless, but impossible, these columns are automatically exculded:


In [274]:
baseball.mean()


Out[274]:
year     2006.92
stint       1.13
g          52.38
ab        136.54
r          18.69
h          35.82
X2b         7.39
X3b         0.55
hr          4.37
rbi        18.47
sb          1.38
cs          0.46
bb         15.49
so         24.08
ibb         1.77
hbp         1.12
sh          1.38
sf          1.20
gidp        3.54
dtype: float64

The important difference between NumPy's functions and Pandas' methods is that the latter have built-in support for handling missing data.


In [275]:
bacteria2


Out[275]:
Firmicutes           NaN
Proteobacteria     632.0
Actinobacteria    1638.0
Bacteroidetes      569.0
dtype: float64

In [276]:
bacteria2.mean()


Out[276]:
946.3333333333334

Sometimes we may not want to ignore missing values, and allow the nan to propagate.


In [277]:
bacteria2.mean(skipna=False)


Out[277]:
nan

Passing axis=1 will summarize over rows instead of columns, which only makes sense in certain situations.


In [278]:
extra_bases = baseball[['X2b','X3b','hr']].sum(axis=1)
extra_bases.sort_values(ascending=False)


Out[278]:
id
88653    69
89439    57
89361    56
89462    55
89396    54
89489    54
89360    54
89371    50
89378    46
89374    46
89389    45
89523    44
89521    42
89463    41
89466    40
88652    39
89438    36
89330    35
89533    33
89481    29
89430    26
89398    26
89347    25
89530    22
89473    20
89495    18
89464    17
89482    16
89499    15
89352    15
         ..
89498     0
89411     0
89525     0
89526     0
88650     0
88649     0
88645     0
88643     0
89341     0
89345     0
89381     0
89493     0
89450     0
89451     0
89372     0
89452     0
89370     0
89460     0
89367     0
89465     0
89384     0
89363     0
89445     0
89388     0
89359     0
89355     0
89354     0
89480     0
89348     0
89420     0
dtype: int64

A useful summarization that gives a quick snapshot of multiple statistics for a Series or DataFrame is describe:


In [279]:
baseball.describe()


Out[279]:
year stint g ab r h X2b X3b hr rbi sb cs bb so ibb hbp sh sf gidp
count 100.00000 100.000000 100.000000 100.000000 100.00000 100.000000 100.000000 100.000000 100.000000 100.00000 100.000000 100.000000 100.000000 100.000000 100.000000 100.00000 100.000000 100.000000 100.000000
mean 2006.92000 1.130000 52.380000 136.540000 18.69000 35.820000 7.390000 0.550000 4.370000 18.47000 1.380000 0.460000 15.490000 24.080000 1.770000 1.12000 1.380000 1.200000 3.540000
std 0.27266 0.337998 48.031299 181.936853 27.77496 50.221807 11.117277 1.445124 7.975537 28.34793 3.694878 1.067613 25.812649 32.804496 5.042957 2.23055 2.919042 2.035046 5.201826
min 2006.00000 1.000000 1.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000
25% 2007.00000 1.000000 9.500000 2.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000 1.000000 0.000000 0.00000 0.000000 0.000000 0.000000
50% 2007.00000 1.000000 33.000000 40.500000 2.00000 8.000000 1.000000 0.000000 0.000000 2.00000 0.000000 0.000000 1.000000 7.000000 0.000000 0.00000 0.000000 0.000000 1.000000
75% 2007.00000 1.000000 83.250000 243.750000 33.25000 62.750000 11.750000 1.000000 6.000000 27.00000 1.000000 0.000000 19.250000 37.250000 1.250000 1.00000 1.000000 2.000000 6.000000
max 2007.00000 2.000000 155.000000 586.000000 107.00000 159.000000 52.000000 12.000000 35.000000 96.00000 22.000000 6.000000 132.000000 134.000000 43.000000 11.00000 14.000000 9.000000 21.000000

describe can detect non-numeric data and sometimes yield useful information about it.


In [280]:
baseball.player.describe()


Out[280]:
count           100
unique           82
top       wickmbo01
freq              2
Name: player, dtype: object

We can also calculate summary statistics across multiple columns, for example, correlation and covariance.

$$cov(x,y) = \sum_i (x_i - \bar{x})(y_i - \bar{y})$$

In [281]:
baseball.hr.cov(baseball.X2b)


Out[281]:
69.076464646464544
$$corr(x,y) = \frac{cov(x,y)}{(n-1)s_x s_y} = \frac{\sum_i (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum_i (x_i - \bar{x})^2 \sum_i (y_i - \bar{y})^2}}$$

In [282]:
baseball.hr.corr(baseball.X2b)


Out[282]:
0.77906151825397418

In [283]:
baseball.ab.corr(baseball.h)


Out[283]:
0.99421740362723765

Try running corr on the entire baseball DataFrame to see what is returned:


In [285]:
# Write answer here
baseball.corr()


Out[285]:
year stint g ab r h X2b X3b hr rbi sb cs bb so ibb hbp sh sf gidp
year 1.000000 0.004384 -0.050874 -0.001360 -0.023315 0.001151 -0.052917 -0.246099 0.060199 0.042812 0.030480 0.058296 0.005626 0.069610 0.015868 -0.000664 -0.012184 -0.007282 0.052131
stint 0.004384 1.000000 -0.257552 -0.216333 -0.209781 -0.206878 -0.196423 -0.085821 -0.209124 -0.205688 -0.120837 -0.055425 -0.190301 -0.214121 -0.118580 -0.195074 -0.091527 -0.155662 -0.224173
g -0.050874 -0.257552 1.000000 0.935910 0.910262 0.929292 0.885847 0.518663 0.802014 0.891563 0.492362 0.520923 0.828572 0.866499 0.514423 0.730161 0.079361 0.767543 0.863041
ab -0.001360 -0.216333 0.935910 1.000000 0.965609 0.994217 0.952249 0.535986 0.843308 0.947911 0.533536 0.577192 0.850803 0.923926 0.506398 0.767210 0.094537 0.840361 0.926632
r -0.023315 -0.209781 0.910262 0.965609 1.000000 0.970560 0.923508 0.500807 0.890060 0.941483 0.596343 0.576454 0.915010 0.879375 0.588882 0.806523 -0.001273 0.839592 0.894724
h 0.001151 -0.206878 0.929292 0.994217 0.970560 1.000000 0.957275 0.514245 0.855163 0.952320 0.530018 0.571629 0.853384 0.906966 0.513009 0.767449 0.045533 0.839737 0.935525
X2b -0.052917 -0.196423 0.885847 0.952249 0.923508 0.957275 1.000000 0.493267 0.779062 0.901751 0.413655 0.477487 0.780012 0.862149 0.453301 0.738226 0.005659 0.819361 0.906860
X3b -0.246099 -0.085821 0.518663 0.535986 0.500807 0.514245 0.493267 1.000000 0.210028 0.369890 0.450421 0.384312 0.350682 0.408800 0.090993 0.217474 0.187012 0.394987 0.411577
hr 0.060199 -0.209124 0.802014 0.843308 0.890060 0.855163 0.779062 0.210028 1.000000 0.948787 0.364346 0.345187 0.916774 0.865929 0.673691 0.767411 -0.145374 0.782038 0.798350
rbi 0.042812 -0.205688 0.891563 0.947911 0.941483 0.952320 0.901751 0.369890 0.948787 1.000000 0.394633 0.435011 0.893945 0.929410 0.582982 0.780899 -0.054670 0.855260 0.906908
sb 0.030480 -0.120837 0.492362 0.533536 0.596343 0.530018 0.413655 0.450421 0.364346 0.394633 1.000000 0.743921 0.491351 0.365841 0.209110 0.413570 0.171910 0.412947 0.431198
cs 0.058296 -0.055425 0.520923 0.577192 0.576454 0.571629 0.477487 0.384312 0.345187 0.435011 0.743921 1.000000 0.425352 0.426658 0.106152 0.337129 0.293397 0.422146 0.456820
bb 0.005626 -0.190301 0.828572 0.850803 0.915010 0.853384 0.780012 0.350682 0.916774 0.893945 0.491351 0.425352 1.000000 0.795751 0.792057 0.742118 -0.044992 0.760932 0.823631
so 0.069610 -0.214121 0.866499 0.923926 0.879375 0.906966 0.862149 0.408800 0.865929 0.929410 0.365841 0.426658 0.795751 1.000000 0.476613 0.742547 0.073624 0.782314 0.846629
ibb 0.015868 -0.118580 0.514423 0.506398 0.588882 0.513009 0.453301 0.090993 0.673691 0.582982 0.209110 0.106152 0.792057 0.476613 1.000000 0.431714 -0.075658 0.451377 0.572355
hbp -0.000664 -0.195074 0.730161 0.767210 0.806523 0.767449 0.738226 0.217474 0.767411 0.780899 0.413570 0.337129 0.742118 0.742547 0.431714 1.000000 -0.104810 0.648882 0.700380
sh -0.012184 -0.091527 0.079361 0.094537 -0.001273 0.045533 0.005659 0.187012 -0.145374 -0.054670 0.171910 0.293397 -0.044992 0.073624 -0.075658 -0.104810 1.000000 -0.002721 0.028924
sf -0.007282 -0.155662 0.767543 0.840361 0.839592 0.839737 0.819361 0.394987 0.782038 0.855260 0.412947 0.422146 0.760932 0.782314 0.451377 0.648882 -0.002721 1.000000 0.785489
gidp 0.052131 -0.224173 0.863041 0.926632 0.894724 0.935525 0.906860 0.411577 0.798350 0.906908 0.431198 0.456820 0.823631 0.846629 0.572355 0.700380 0.028924 0.785489 1.000000

If we have a DataFrame with a hierarchical index (or indices), summary statistics can be applied with respect to any of the index levels:


In [286]:
mb.head()


Out[286]:
Group Tissue Stool
Taxon Patient
Firmicutes 1 0 136 4182
2 1 1174 703
3 0 408 3946
4 1 831 8605
5 0 693 50

In [287]:
mb.sum(level='Taxon')


Out[287]:
Group Tissue Stool
Taxon
Actinobacteria 7 6167 1615
Bacteroidetes 7 8880 4276
Firmicutes 7 9634 30172
Other 7 2868 242
Proteobacteria 7 42508 12483

Writing Data to Files

As well as being able to read several data input formats, Pandas can also export data to a variety of storage formats. We will bring your attention to just a couple of these.


In [288]:
mb.to_csv("mb.csv")

The to_csv method writes a DataFrame to a comma-separated values (csv) file. You can specify custom delimiters (via sep argument), how missing values are written (via na_rep argument), whether the index is writen (via index argument), whether the header is included (via header argument), among other options.

An efficient way of storing data to disk is in binary format. Pandas supports this using Python’s built-in pickle serialization.


In [289]:
baseball.to_pickle("baseball_pickle")

The complement to to_pickle is the read_pickle function, which restores the pickle to a DataFrame or Series:


In [290]:
pd.read_pickle("baseball_pickle")


Out[290]:
player year stint team lg g ab r h X2b ... rbi sb cs bb so ibb hbp sh sf gidp
id
88641 womacto01 2006 2 CHN NL 19 50 6 14 1 ... 2.0 1.0 1.0 4 4.0 0.0 0.0 3.0 0.0 0.0
88643 schilcu01 2006 1 BOS AL 31 2 0 1 0 ... 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
88645 myersmi01 2006 1 NYA AL 62 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
88649 helliri01 2006 1 MIL NL 20 3 0 0 0 ... 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0 0.0 0.0
88650 johnsra05 2006 1 NYA AL 33 6 0 1 0 ... 0.0 0.0 0.0 0 4.0 0.0 0.0 0.0 0.0 0.0
88652 finlest01 2006 1 SFN NL 139 426 66 105 21 ... 40.0 7.0 0.0 46 55.0 2.0 2.0 3.0 4.0 6.0
88653 gonzalu01 2006 1 ARI NL 153 586 93 159 52 ... 73.0 0.0 1.0 69 58.0 10.0 7.0 0.0 6.0 14.0
88662 seleaa01 2006 1 LAN NL 28 26 2 5 1 ... 0.0 0.0 0.0 1 7.0 0.0 0.0 6.0 0.0 1.0
89177 francju01 2007 2 ATL NL 15 40 1 10 3 ... 8.0 0.0 0.0 4 10.0 1.0 0.0 0.0 1.0 1.0
89178 francju01 2007 1 NYN NL 40 50 7 10 0 ... 8.0 2.0 1.0 10 13.0 0.0 0.0 0.0 1.0 1.0
89330 zaungr01 2007 1 TOR AL 110 331 43 80 24 ... 52.0 0.0 0.0 51 55.0 8.0 2.0 1.0 6.0 9.0
89333 witasja01 2007 1 TBA AL 3 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89334 williwo02 2007 1 HOU NL 33 59 3 6 0 ... 2.0 0.0 0.0 0 25.0 0.0 0.0 5.0 0.0 1.0
89335 wickmbo01 2007 2 ARI NL 8 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89336 wickmbo01 2007 1 ATL NL 47 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89337 whitero02 2007 1 MIN AL 38 109 8 19 4 ... 20.0 0.0 0.0 6 19.0 0.0 3.0 0.0 1.0 2.0
89338 whiteri01 2007 1 HOU NL 20 1 0 0 0 ... 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
89339 wellsda01 2007 2 LAN NL 7 15 2 4 1 ... 1.0 0.0 0.0 0 6.0 0.0 0.0 0.0 0.0 0.0
89340 wellsda01 2007 1 SDN NL 22 38 1 4 0 ... 0.0 0.0 0.0 0 12.0 0.0 0.0 4.0 0.0 0.0
89341 weathda01 2007 1 CIN NL 67 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89343 walketo04 2007 1 OAK AL 18 48 5 13 1 ... 4.0 0.0 0.0 2 4.0 0.0 0.0 0.0 2.0 2.0
89345 wakefti01 2007 1 BOS AL 1 2 0 0 0 ... 0.0 0.0 0.0 0 2.0 0.0 0.0 0.0 0.0 0.0
89347 vizquom01 2007 1 SFN NL 145 513 54 126 18 ... 51.0 14.0 6.0 44 48.0 6.0 1.0 14.0 3.0 14.0
89348 villoro01 2007 1 NYA AL 6 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89352 valenjo03 2007 1 NYN NL 51 166 18 40 11 ... 18.0 2.0 1.0 15 28.0 4.0 0.0 1.0 1.0 5.0
89354 trachst01 2007 2 CHN NL 4 7 0 1 0 ... 0.0 0.0 0.0 0 1.0 0.0 0.0 0.0 0.0 0.0
89355 trachst01 2007 1 BAL AL 3 5 0 0 0 ... 0.0 0.0 0.0 0 3.0 0.0 0.0 0.0 0.0 0.0
89359 timlimi01 2007 1 BOS AL 4 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89360 thomeji01 2007 1 CHA AL 130 432 79 119 19 ... 96.0 0.0 1.0 95 134.0 11.0 6.0 0.0 3.0 10.0
89361 thomafr04 2007 1 TOR AL 155 531 63 147 30 ... 95.0 0.0 0.0 81 94.0 3.0 7.0 0.0 5.0 14.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
89460 guarded01 2007 1 CIN NL 15 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89462 griffke02 2007 1 CIN NL 144 528 78 146 24 ... 93.0 6.0 1.0 85 99.0 14.0 1.0 0.0 9.0 14.0
89463 greensh01 2007 1 NYN NL 130 446 62 130 30 ... 46.0 11.0 1.0 37 62.0 4.0 5.0 1.0 1.0 14.0
89464 graffto01 2007 1 MIL NL 86 231 34 55 8 ... 30.0 0.0 1.0 24 44.0 6.0 3.0 0.0 2.0 7.0
89465 gordoto01 2007 1 PHI NL 44 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89466 gonzalu01 2007 1 LAN NL 139 464 70 129 23 ... 68.0 6.0 2.0 56 56.0 4.0 4.0 0.0 2.0 11.0
89467 gomezch02 2007 2 CLE AL 19 53 4 15 2 ... 5.0 0.0 0.0 0 6.0 0.0 0.0 1.0 1.0 1.0
89468 gomezch02 2007 1 BAL AL 73 169 17 51 10 ... 16.0 1.0 2.0 10 20.0 1.0 0.0 5.0 1.0 5.0
89469 glavito02 2007 1 NYN NL 33 56 3 12 1 ... 4.0 0.0 0.0 6 5.0 0.0 0.0 12.0 1.0 0.0
89473 floydcl01 2007 1 CHN NL 108 282 40 80 10 ... 45.0 0.0 0.0 35 47.0 5.0 5.0 0.0 0.0 6.0
89474 finlest01 2007 1 COL NL 43 94 9 17 3 ... 2.0 0.0 0.0 8 4.0 1.0 0.0 0.0 0.0 2.0
89480 embreal01 2007 1 OAK AL 4 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89481 edmonji01 2007 1 SLN NL 117 365 39 92 15 ... 53.0 0.0 2.0 41 75.0 2.0 0.0 2.0 3.0 9.0
89482 easleda01 2007 1 NYN NL 76 193 24 54 6 ... 26.0 0.0 1.0 19 35.0 1.0 5.0 0.0 1.0 2.0
89489 delgaca01 2007 1 NYN NL 139 538 71 139 30 ... 87.0 4.0 0.0 52 118.0 8.0 11.0 0.0 6.0 12.0
89493 cormirh01 2007 1 CIN NL 6 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89494 coninje01 2007 2 NYN NL 21 41 2 8 2 ... 5.0 0.0 0.0 7 8.0 2.0 0.0 1.0 1.0 1.0
89495 coninje01 2007 1 CIN NL 80 215 23 57 11 ... 32.0 4.0 0.0 20 28.0 0.0 0.0 1.0 6.0 4.0
89497 clemero02 2007 1 NYA AL 2 2 0 1 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89498 claytro01 2007 2 BOS AL 8 6 1 0 0 ... 0.0 0.0 0.0 0 3.0 0.0 0.0 0.0 0.0 2.0
89499 claytro01 2007 1 TOR AL 69 189 23 48 14 ... 12.0 2.0 1.0 14 50.0 0.0 1.0 3.0 3.0 8.0
89501 cirilje01 2007 2 ARI NL 28 40 6 8 4 ... 6.0 0.0 0.0 4 6.0 0.0 0.0 0.0 0.0 1.0
89502 cirilje01 2007 1 MIN AL 50 153 18 40 9 ... 21.0 2.0 0.0 15 13.0 0.0 1.0 3.0 2.0 9.0
89521 bondsba01 2007 1 SFN NL 126 340 75 94 14 ... 66.0 5.0 0.0 132 54.0 43.0 3.0 0.0 2.0 13.0
89523 biggicr01 2007 1 HOU NL 141 517 68 130 31 ... 50.0 4.0 3.0 23 112.0 0.0 3.0 7.0 5.0 5.0
89525 benitar01 2007 2 FLO NL 34 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89526 benitar01 2007 1 SFN NL 19 0 0 0 0 ... 0.0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0.0
89530 ausmubr01 2007 1 HOU NL 117 349 38 82 16 ... 25.0 6.0 1.0 37 74.0 3.0 6.0 4.0 1.0 11.0
89533 aloumo01 2007 1 NYN NL 87 328 51 112 19 ... 49.0 3.0 0.0 27 30.0 5.0 2.0 0.0 3.0 13.0
89534 alomasa02 2007 1 NYN NL 8 22 1 3 1 ... 0.0 0.0 0.0 0 3.0 0.0 0.0 0.0 0.0 0.0

100 rows × 22 columns

As Wes warns in his book, it is recommended that binary storage of data via pickle only be used as a temporary storage format, in situations where speed is relevant. This is because there is no guarantee that the pickle format will not change with future versions of Python.

Advanced Exercise: Compiling Ebola Data

The Data/ebola folder contains summarized reports of Ebola cases from three countries during the recent outbreak of the disease in West Africa. For each country, there are daily reports that contain various information about the outbreak in several cities in each country.

From these data files, use pandas to import them and create a single data frame that includes the daily totals of new cases and deaths for each country.


In [186]:
# from http://stackoverflow.com/questions/20906474/import-multiple-csv-files-into-pandas-and-concatenate-into-one-dataframe
import glob

# Guinea
path ='Data/ebola/guinea_data' # file path
allFiles = glob.glob(path + "/*.csv")
guinea_frame = pd.DataFrame()
list_ = []
for file_ in allFiles:
    df = pd.read_csv(file_,index_col=None, header=0)
    list_.append(df)
guinea_frame = pd.concat(list_)

# Liberia
path ='Data/ebola/liberia_data' # file path
allFiles = glob.glob(path + "/*.csv")
liberia_frame = pd.DataFrame()
list_ = []
for file_ in allFiles:
    df = pd.read_csv(file_,index_col=None, header=0)
    list_.append(df)
liberia_frame = pd.concat(list_)

# Sierra Leone
path ='Data/ebola/sl_data' # file path
allFiles = glob.glob(path + "/*.csv")
sl_frame = pd.DataFrame()
list_ = []
for file_ in allFiles:
    df = pd.read_csv(file_,index_col=None, header=0)
    list_.append(df)
sl_frame = pd.concat(list_)

In [187]:
#do isin to get only New cases of confirmed and New deaths registered for each country
guinea_frame_column = guinea_frame[guinea_frame['Description'].isin(['New cases of confirmed', 'New deaths registered'])]
guinea_frame_column.head()


Out[187]:
Beyla Boffa Conakry Coyah Dabola Dalaba Date Description Dinguiraye Dubreka ... Kouroussa Lola Macenta Mzerekore Nzerekore Pita Siguiri Telimele Totals Yomou
2 NaN 0 1 NaN 0 NaN 2014-08-04 New cases of confirmed 0 0 ... 0 NaN 0 NaN 0 0 0 0 4 NaN
2 NaN NaN NaN NaN NaN NaN 2014-08-26 New cases of confirmed NaN 1 ... NaN NaN 5 NaN NaN NaN NaN NaN 10 3
9 NaN 0 0 NaN 0 NaN 2014-08-26 New deaths registered 0 0 ... 0 NaN 2 0 NaN 0 0 0 5 3
2 NaN 0 4 NaN 0 NaN 2014-08-27 New cases of confirmed 0 1 ... 0 NaN 5 NaN 0 0 0 0 10 0
9 NaN NaN 0 NaN 0 NaN 2014-08-27 New deaths registered 0 NaN ... NaN NaN 2 NaN NaN NaN NaN 0 2 NaN

5 rows × 25 columns


In [188]:
#select only the relevant columns for us - date, description, totals
guinea_frame_new= guinea_frame_column[['Date', 'Description', 'Totals']]
guinea_frame_new.head()


Out[188]:
Date Description Totals
2 2014-08-04 New cases of confirmed 4
2 2014-08-26 New cases of confirmed 10
9 2014-08-26 New deaths registered 5
2 2014-08-27 New cases of confirmed 10
9 2014-08-27 New deaths registered 2

References

Python for Data Analysis Wes McKinney


In [175]:
#do isin to get only New cases of confirmed and New deaths registered for each country
liberia_frame_column = liberia_frame[liberia_frame['Variable'].isin(['New case/s (confirmed)', 'Newly reported deaths'])]
liberia_frame_column.head()


Out[175]:
Bomi County Bong County Date Gbarpolu County Grand Bassa Grand Cape Mount Grand Gedeh Grand Kru Lofa County Margibi County Maryland County Montserrado County National Nimba County River Gee County RiverCess County Sinoe County Unnamed: 18 Variable
3 NaN NaN 6/16/2014 NaN NaN NaN NaN NaN 1.0 NaN NaN 0.0 2.0 NaN NaN NaN NaN NaN Newly reported deaths
25 NaN NaN 6/16/2014 NaN NaN NaN NaN NaN 1.0 NaN NaN 0.0 1.0 NaN NaN NaN NaN NaN New case/s (confirmed)
3 NaN NaN 6/17/2014 NaN NaN NaN NaN NaN 0.0 NaN NaN 0.0 0.0 NaN NaN NaN NaN NaN Newly reported deaths
25 NaN NaN 6/17/2014 NaN NaN NaN NaN NaN 0.0 NaN NaN 0.0 0.0 NaN NaN NaN NaN NaN New case/s (confirmed)
3 NaN NaN 6/22/2014 NaN NaN NaN NaN NaN 2.0 NaN NaN 2.0 4.0 NaN NaN NaN NaN NaN Newly reported deaths

In [176]:
#select only the relevant columns for us - date, variable, national
liberia_frame_new= liberia_frame_column[['Date', 'Variable', 'National']]
liberia_frame_new.head()


Out[176]:
Date Variable National
3 6/16/2014 Newly reported deaths 2.0
25 6/16/2014 New case/s (confirmed) 1.0
3 6/17/2014 Newly reported deaths 0.0
25 6/17/2014 New case/s (confirmed) 0.0
3 6/22/2014 Newly reported deaths 4.0

In [180]:
#do isin to get only New cases of confirmed and New deaths registered for each country
sl_frame_column = sl_frame[sl_frame['variable'].isin(['new_confirmed', 'death_confirmed'])]
sl_frame_column.head()


Out[180]:
34 Military Hospital Bo Bo EMC Bombali Bonthe Hastings-F/Town Kailahun Kambia Kenema Kenema (IFRC) ... Port Loko Pujehun Tonkolili Unnamed: 18 Western area Western area combined Western area rural Western area urban date variable
4 NaN 0 NaN 0 0 NaN 0 0 9 NaN ... 2 0 0 NaN NaN NaN 0 0 2014-08-12 new_confirmed
11 NaN 2 NaN 1 0 NaN 151 0 104 NaN ... 1 0 0 NaN NaN NaN 0 2 2014-08-12 death_confirmed
4 NaN 1 NaN 0 0 NaN 11 0 1 NaN ... 0 0 0 NaN NaN NaN 0 2 2014-08-13 new_confirmed
11 NaN 2 NaN 1 0 NaN 157 0 107 NaN ... 1 0 0 NaN NaN NaN 0 2 2014-08-13 death_confirmed
4 NaN 0 NaN 0 0 NaN 0 0 8 NaN ... 2 0 0 NaN NaN NaN 2 0 2014-08-14 new_confirmed

5 rows × 27 columns


In [181]:
#select only the relevant columns for us - date, variable, national
sl_frame_new= sl_frame_column[['date', 'variable', 'National']]
sl_frame_new.head()


Out[181]:
date variable National
4 2014-08-12 new_confirmed 11
11 2014-08-12 death_confirmed 264
4 2014-08-13 new_confirmed 15
11 2014-08-13 death_confirmed 273
4 2014-08-14 new_confirmed 13

In [231]:
#for each country
#made the dates the index and drop the date column
#add a column with the Country name at the end
guinea_frame_new.index = guinea_frame_new.Date
guinea_drop = guinea_frame_new.drop('Date', axis=1)
guinea_drop['Country']=['Guinea']*len(guinea_drop)
guinea_drop.head()


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-231-9b069ae2951e> in <module>()
      5 guinea_drop = guinea_frame_new.drop('Date', axis=1)
      6 guinea_drop['Country']=['Guinea']*len(guinea_drop)
----> 7 guinea_drop['Date']=pd.to_datetime(guinea_drop.Date)
      8 guinea_drop.head()

/Users/paramoed/anaconda/lib/python3.5/site-packages/pandas/core/generic.py in __getattr__(self, name)
   2670             if name in self._info_axis:
   2671                 return self[name]
-> 2672             return object.__getattribute__(self, name)
   2673 
   2674     def __setattr__(self, name, value):

AttributeError: 'DataFrame' object has no attribute 'Date'

In [208]:
liberia_frame_new.index = liberia_frame_new.Date
liberia_drop = liberia_frame_new.drop('Date', axis=1)
liberia_drop['Country']=['Liberia']*len(liberia_drop)
#liberia_drop['Date'] = liberia_drop['Date'].astype('datetime64[ns]')
liberia_drop.head()


Out[208]:
Variable National Country
Date
6/16/2014 Newly reported deaths 2.0 Liberia
6/16/2014 New case/s (confirmed) 1.0 Liberia
6/17/2014 Newly reported deaths 0.0 Liberia
6/17/2014 New case/s (confirmed) 0.0 Liberia
6/22/2014 Newly reported deaths 4.0 Liberia

In [210]:
sl_frame_new.index = sl_frame_new.date
sl_drop = sl_frame_new.drop('date', axis=1)
sl_drop['Country']=['Sierra Leone']*len(sl_drop)
sl_drop['Country']=['Sierra Leone']
#sl_drop['date'] = sl_drop['date'].astype('datetime64[ns]')
sl_drop.head()


Out[210]:
variable National Country
date
2014-08-12 new_confirmed 11 Sierra Leone
2014-08-12 death_confirmed 264 Sierra Leone
2014-08-13 new_confirmed 15 Sierra Leone
2014-08-13 death_confirmed 273 Sierra Leone
2014-08-14 new_confirmed 13 Sierra Leone

In [233]:
#Change the names of the columns to the same
guinea_drop.columns = liberia_drop.columns = sl_drop.columns = ['Description', 'National Total', 'Country']
#Change the names of the date column to the same
guinea_drop.index.name = liberia_drop.index.name = sl_drop.index.name = ['Date']

In [240]:
#Merge the files
dataframe = pd.concat([liberia_drop,guinea_drop,sl_drop])
dataframe[['Country','Description','National Total']]


Out[240]:
Country Description National Total
[Date]
6/16/2014 Liberia Newly reported deaths 2
6/16/2014 Liberia New case/s (confirmed) 1
6/17/2014 Liberia Newly reported deaths 0
6/17/2014 Liberia New case/s (confirmed) 0
6/22/2014 Liberia Newly reported deaths 4
6/22/2014 Liberia New case/s (confirmed) 5
6/24/2014 Liberia Newly reported deaths 4
6/24/2014 Liberia New case/s (confirmed) 4
6/25/14 Liberia Newly reported deaths 3
6/25/14 Liberia New case/s (confirmed) 2
6/28/2014 Liberia Newly reported deaths 1
6/28/2014 Liberia New case/s (confirmed) 1
6/29/2014 Liberia Newly reported deaths 0
6/29/2014 Liberia New case/s (confirmed) 2
7/1/2014 Liberia Newly reported deaths 5
7/1/2014 Liberia New case/s (confirmed) 2
7/2/2014 Liberia Newly reported deaths 5
7/2/2014 Liberia New case/s (confirmed) 0
7/3/2014 Liberia Newly reported deaths 4
7/3/2014 Liberia New case/s (confirmed) 1
7/7/2014 Liberia Newly reported deaths 2
7/7/2014 Liberia New case/s (confirmed) 5
7/8/2014 Liberia Newly reported deaths 2
7/8/2014 Liberia New case/s (confirmed) 0
7/10/2014 Liberia Newly reported deaths 4
7/10/2014 Liberia New case/s (confirmed) 0
7/13/2014 Liberia Newly reported deaths 3
7/13/2014 Liberia New case/s (confirmed) 0
7/17/2014 Liberia Newly reported deaths 3
7/17/2014 Liberia New case/s (confirmed) 0
... ... ... ...
2014-11-17 Sierra Leone new_confirmed 53
2014-11-17 Sierra Leone death_confirmed 1223
2014-11-18 Sierra Leone new_confirmed 43
2014-11-18 Sierra Leone death_confirmed 1240
2014-11-19 Sierra Leone new_confirmed NaN
2014-11-19 Sierra Leone death_confirmed NaN
2014-11-20 Sierra Leone new_confirmed 94
2014-11-20 Sierra Leone death_confirmed 1282
2014-11-21 Sierra Leone new_confirmed 51
2014-11-21 Sierra Leone death_confirmed 1303
2014-11-22 Sierra Leone new_confirmed 47
2014-11-22 Sierra Leone death_confirmed 1333
2014-11-23 Sierra Leone new_confirmed 39
2014-11-23 Sierra Leone death_confirmed 1364
2014-11-24 Sierra Leone new_confirmed 83
2014-11-24 Sierra Leone death_confirmed 1397
2014-11-28 Sierra Leone new_confirmed 84
2014-11-28 Sierra Leone death_confirmed 1481
2014-11-29 Sierra Leone new_confirmed 64
2014-11-29 Sierra Leone death_confirmed 1496
2014-12-01 Sierra Leone new_confirmed 72
2014-12-01 Sierra Leone death_confirmed 1549
2014-12-04 Sierra Leone new_confirmed 37
2014-12-04 Sierra Leone death_confirmed 1648
2014-12-05 Sierra Leone new_confirmed 54
2014-12-05 Sierra Leone death_confirmed 1669
2014-12-06 Sierra Leone new_confirmed NaN
2014-12-06 Sierra Leone death_confirmed 1708
2014-12-13 Sierra Leone new_confirmed NaN
2014-12-13 Sierra Leone death_confirmed NaN

449 rows × 3 columns


In [ ]: